Commit d9153a13 authored by Hans Muller's avatar Hans Muller

Update shadow rendering

Shadows now render as three seprate MaskFilter.blur components per the most recent Material spec.

The shadows Map was replaced by a similar Map called elevationToShadow with entries that match the 10 elevations specifed by http://www.google.com/design/spec/what-is-material/elevation-shadows.html.

The "level" property (many classes) is now called "elevation", to match the Material spec.

BoxShadow now includes a spreadRadius parameter - as in CSS box-shadow. Renamed the BoxShadow blur property to blurRadius to further align BoxShadow with CSS box-shadow.
parent b20a1f4a
......@@ -13,7 +13,7 @@ void main() {
gradient: new RadialGradient(
center: Point.origin, radius: 500.0,
colors: <Color>[Colors.yellow[500], Colors.blue[500]]),
boxShadow: shadows[3])
boxShadow: elevationToShadow[8])
);
var paddedBox = new RenderPadding(
padding: const EdgeDims.all(50.0),
......
......@@ -146,7 +146,7 @@ class StockHomeState extends State<StockHome> {
Widget buildToolBar() {
return new ToolBar(
level: 0,
elevation: 0,
left: new IconButton(
icon: "navigation/menu",
onPressed: _showDrawer
......
......@@ -100,7 +100,7 @@ Widget statusBox(Widget child) {
decoration: const BoxDecoration(
boxShadow: const <BoxShadow>[
const BoxShadow(
color: mediumGray, offset: const Offset(6.0, 6.0), blur: 5.0)
color: mediumGray, offset: const Offset(6.0, 6.0), blurRadius: 5.0)
],
backgroundColor: darkGray
),
......
......@@ -23,7 +23,7 @@ class Card extends StatelessComponent {
child: new Material(
color: color,
type: MaterialType.card,
level: 2,
elevation: 8,
child: child
)
);
......
......@@ -102,7 +102,7 @@ class Dialog extends StatelessComponent {
child: new ConstrainedBox(
constraints: new BoxConstraints(minWidth: 280.0),
child: new Material(
level: 4,
elevation: 24,
color: _getColor(context),
type: MaterialType.card,
child: new IntrinsicWidth(
......
......@@ -36,7 +36,7 @@ class _Drawer extends StatelessComponent {
child: new ConstrainedBox(
constraints: const BoxConstraints.expand(width: _kWidth),
child: new Material(
level: route.level,
elevation: route.elevation,
child: route.child
)
)
......@@ -51,10 +51,10 @@ enum _DrawerState {
}
class _DrawerRoute extends OverlayRoute {
_DrawerRoute({ this.child, this.level });
_DrawerRoute({ this.child, this.elevation });
final Widget child;
final int level;
final int elevation;
List<WidgetBuilder> get builders => <WidgetBuilder>[ _build ];
......@@ -212,6 +212,6 @@ class _DrawerControllerState extends State<_DrawerController> {
}
}
void showDrawer({ BuildContext context, Widget child, int level: 3 }) {
Navigator.of(context).push(new _DrawerRoute(child: child, level: level));
void showDrawer({ BuildContext context, Widget child, int elevation: 16 }) {
Navigator.of(context).push(new _DrawerRoute(child: child, elevation: elevation));
}
......@@ -85,7 +85,7 @@ class _DropdownMenu extends StatusTransitionComponent {
final BoxPainter menuPainter = new BoxPainter(new BoxDecoration(
backgroundColor: Theme.of(context).canvasColor,
borderRadius: 2.0,
boxShadow: shadows[route.level]
boxShadow: elevationToShadow[route.elevation]
));
final RenderBox renderBox = Navigator.of(context).context.findRenderObject();
......@@ -129,13 +129,13 @@ class _MenuRoute extends TransitionRoute {
this.items,
this.selectedIndex,
this.rect,
this.level: 4
this.elevation: 8
});
final Completer completer;
final Rect rect;
final List<DropdownMenuItem> items;
final int level;
final int elevation;
final int selectedIndex;
bool get opaque => false;
......@@ -183,13 +183,13 @@ class DropdownButton<T> extends StatelessComponent {
this.items,
this.value,
this.onChanged,
this.level: 4
this.elevation: 8
}) : super(key: key);
final List<DropdownMenuItem<T>> items;
final T value;
final ValueChanged<T> onChanged;
final int level;
final int elevation;
void _showDropdown(BuildContext context, int selectedIndex, GlobalKey indexedStackKey) {
final RenderBox renderBox = indexedStackKey.currentContext.findRenderObject();
......@@ -200,7 +200,7 @@ class DropdownButton<T> extends StatelessComponent {
items: items,
selectedIndex: selectedIndex,
rect: _kMenuHorizontalPadding.inflateRect(rect),
level: level
elevation: elevation
));
completer.future.then((T newValue) {
if (onChanged != null)
......
......@@ -22,7 +22,7 @@ class FlatButton extends MaterialButton {
class _FlatButtonState extends MaterialButtonState<FlatButton> {
int get level => 0;
int get elevation => 0;
Color getColor(BuildContext context, { bool highlight }) {
if (!config.enabled || !highlight)
......
......@@ -50,7 +50,7 @@ class _FloatingActionButtonState extends State<FloatingActionButton> {
return new Material(
color: materialColor,
type: MaterialType.circle,
level: _highlight ? 3 : 2,
elevation: _highlight ? 12 : 6,
child: new ClipOval(
child: new Container(
width: _kSize,
......
......@@ -23,16 +23,16 @@ class Material extends StatelessComponent {
Key key,
this.child,
this.type: MaterialType.canvas,
this.level: 0,
this.elevation: 0,
this.color,
this.textStyle
}) : super(key: key) {
assert(level != null);
assert(elevation != null);
}
final Widget child;
final MaterialType type;
final int level;
final int elevation;
final Color color;
final TextStyle textStyle;
......@@ -72,7 +72,7 @@ class Material extends StatelessComponent {
decoration: new BoxDecoration(
backgroundColor: _getBackgroundColor(context),
borderRadius: _kEdges[type],
boxShadow: level == 0 ? null : shadows[level],
boxShadow: elevation == 0 ? null : elevationToShadow[elevation],
shape: type == MaterialType.circle ? Shape.circle : Shape.rectangle
),
child: contents
......
......@@ -56,7 +56,7 @@ abstract class MaterialButton extends StatefulComponent {
abstract class MaterialButtonState<T extends MaterialButton> extends State<T> {
bool highlight = false;
int get level;
int get elevation;
Color getColor(BuildContext context, { bool highlight });
ThemeBrightness getColorBrightness(BuildContext context);
......@@ -102,7 +102,7 @@ abstract class MaterialButtonState<T extends MaterialButton> extends State<T> {
margin: new EdgeDims.all(8.0),
child: new Material(
type: MaterialType.button,
level: level,
elevation: elevation,
textStyle: Theme.of(context).text.button.copyWith(color: getTextColor(context)),
child: new InkWell(
onTap: config.enabled ? config.onPressed : null,
......
......@@ -34,7 +34,7 @@ class _PopupMenu extends StatelessComponent {
final BoxPainter painter = new BoxPainter(new BoxDecoration(
backgroundColor: Theme.of(context).canvasColor,
borderRadius: 2.0,
boxShadow: shadows[route.level]
boxShadow: elevationToShadow[route.elevation]
));
double unit = 1.0 / (route.items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade.
......@@ -93,11 +93,11 @@ class _PopupMenu extends StatelessComponent {
}
class _MenuRoute extends ModalRoute {
_MenuRoute({ Completer completer, this.position, this.items, this.level }) : super(completer: completer);
_MenuRoute({ Completer completer, this.position, this.items, this.elevation }) : super(completer: completer);
final ModalPosition position;
final List<PopupMenuItem> items;
final int level;
final int elevation;
Performance createPerformance() {
Performance result = super.createPerformance();
......@@ -113,13 +113,13 @@ class _MenuRoute extends ModalRoute {
Widget buildPage(BuildContext context) => new _PopupMenu(route: this);
}
Future showMenu({ BuildContext context, ModalPosition position, List<PopupMenuItem> items, int level: 4 }) {
Future showMenu({ BuildContext context, ModalPosition position, List<PopupMenuItem> items, int elevation: 8 }) {
Completer completer = new Completer();
Navigator.of(context).pushEphemeral(new _MenuRoute(
completer: completer,
position: position,
items: items,
level: level
elevation: elevation
));
return completer.future;
}
......@@ -22,7 +22,7 @@ class RaisedButton extends MaterialButton {
class _RaisedButtonState extends MaterialButtonState<RaisedButton> {
int get level => config.enabled ? (highlight ? 2 : 1) : 0;
int get elevation => config.enabled ? (highlight ? 8 : 2) : 0;
Color getColor(BuildContext context, { bool highlight }) {
if (config.enabled) {
......
......@@ -6,55 +6,72 @@ import 'dart:ui' show Color, Offset;
import 'package:flutter/painting.dart';
const Map<int, List<BoxShadow>> shadows = const <int, List<BoxShadow>>{
// Based on http://www.google.com/design/spec/what-is-material/elevation-shadows.html
// Currently, only the elevation values that are bound to one or more components are
// defined here.
const Color _kKeyUmbraOpacity = const Color(0x33000000); // alpha = 0.2
const Color _kKeyPenumbraOpacity = const Color(0x24000000); // alpha = 0.14
const Color _kAmbientShadowOpacity = const Color(0x1F000000); // alpha = 0.12
const Map<int, List<BoxShadow>> elevationToShadow = const <int, List<BoxShadow>>{
1: const <BoxShadow>[
const BoxShadow(
color: const Color(0x1F000000),
offset: const Offset(0.0, 1.0),
blur: 3.0),
const BoxShadow(
color: const Color(0x3D000000),
offset: const Offset(0.0, 1.0),
blur: 2.0),
const BoxShadow(offset: const Offset(0.0, 2.0), blurRadius: 1.0, spreadRadius: -1.0, color: _kKeyUmbraOpacity),
const BoxShadow(offset: const Offset(0.0, 1.0), blurRadius: 1.0, spreadRadius: 0.0, color: _kKeyPenumbraOpacity),
const BoxShadow(offset: const Offset(0.0, 1.0), blurRadius: 3.0, spreadRadius: 0.0, color: _kAmbientShadowOpacity),
],
2: const <BoxShadow>[
const BoxShadow(
color: const Color(0x29000000),
offset: const Offset(0.0, 3.0),
blur: 6.0),
const BoxShadow(
color: const Color(0x3B000000),
offset: const Offset(0.0, 3.0),
blur: 6.0),
const BoxShadow(offset: const Offset(0.0, 3.0), blurRadius: 1.0, spreadRadius: -2.0, color: _kKeyUmbraOpacity),
const BoxShadow(offset: const Offset(0.0, 2.0), blurRadius: 2.0, spreadRadius: 0.0, color: _kKeyPenumbraOpacity),
const BoxShadow(offset: const Offset(0.0, 1.0), blurRadius: 5.0, spreadRadius: 0.0, color: _kAmbientShadowOpacity),
],
3: const <BoxShadow>[
const BoxShadow(
color: const Color(0x30000000),
offset: const Offset(0.0, 10.0),
blur: 20.0),
const BoxShadow(
color: const Color(0x3B000000),
offset: const Offset(0.0, 6.0),
blur: 6.0),
const BoxShadow(offset: const Offset(0.0, 3.0), blurRadius: 3.0, spreadRadius: -2.0, color: _kKeyUmbraOpacity),
const BoxShadow(offset: const Offset(0.0, 3.0), blurRadius: 4.0, spreadRadius: 0.0, color: _kKeyPenumbraOpacity),
const BoxShadow(offset: const Offset(0.0, 1.0), blurRadius: 8.0, spreadRadius: 0.0, color: _kAmbientShadowOpacity),
],
4: const <BoxShadow>[
const BoxShadow(
color: const Color(0x40000000),
offset: const Offset(0.0, 14.0),
blur: 28.0),
const BoxShadow(
color: const Color(0x38000000),
offset: const Offset(0.0, 10.0),
blur: 10.0),
],
5: const <BoxShadow>[
const BoxShadow(
color: const Color(0x4E000000),
offset: const Offset(0.0, 19.0),
blur: 28.0),
const BoxShadow(
color: const Color(0x38000000),
offset: const Offset(0.0, 15.0),
blur: 12.0),
const BoxShadow(offset: const Offset(0.0, 2.0), blurRadius: 4.0, spreadRadius: -1.0, color: _kKeyUmbraOpacity),
const BoxShadow(offset: const Offset(0.0, 4.0), blurRadius: 5.0, spreadRadius: 0.0, color: _kKeyPenumbraOpacity),
const BoxShadow(offset: const Offset(0.0, 1.0), blurRadius: 10.0, spreadRadius: 0.0, color: _kAmbientShadowOpacity),
],
6: const <BoxShadow>[
const BoxShadow(offset: const Offset(0.0, 3.0), blurRadius: 5.0, spreadRadius: -1.0, color: _kKeyUmbraOpacity),
const BoxShadow(offset: const Offset(0.0, 6.0), blurRadius: 10.0, spreadRadius: 0.0, color: _kKeyPenumbraOpacity),
const BoxShadow(offset: const Offset(0.0, 1.0), blurRadius: 18.0, spreadRadius: 0.0, color: _kAmbientShadowOpacity),
],
8: const <BoxShadow>[
const BoxShadow(offset: const Offset(0.0, 5.0), blurRadius: 5.0, spreadRadius: -3.0, color: _kKeyUmbraOpacity),
const BoxShadow(offset: const Offset(0.0, 8.0), blurRadius: 10.0, spreadRadius: 1.0, color: _kKeyPenumbraOpacity),
const BoxShadow(offset: const Offset(0.0, 3.0), blurRadius: 14.0, spreadRadius: 2.0, color: _kAmbientShadowOpacity),
],
9: const <BoxShadow>[
const BoxShadow(offset: const Offset(0.0, 5.0), blurRadius: 6.0, spreadRadius: -3.0, color: _kKeyUmbraOpacity),
const BoxShadow(offset: const Offset(0.0, 9.0), blurRadius: 12.0, spreadRadius: 1.0, color: _kKeyPenumbraOpacity),
const BoxShadow(offset: const Offset(0.0, 3.0), blurRadius: 16.0, spreadRadius: 2.0, color: _kAmbientShadowOpacity),
],
12: const <BoxShadow>[
const BoxShadow(offset: const Offset(0.0, 7.0), blurRadius: 8.0, spreadRadius: -4.0, color: _kKeyUmbraOpacity),
const BoxShadow(offset: const Offset(0.0, 12.0), blurRadius: 17.0, spreadRadius: 2.0, color: _kKeyPenumbraOpacity),
const BoxShadow(offset: const Offset(0.0, 5.0), blurRadius: 22.0, spreadRadius: 4.0, color: _kAmbientShadowOpacity),
],
16: const <BoxShadow>[
const BoxShadow(offset: const Offset(0.0, 8.0), blurRadius: 10.0, spreadRadius: -5.0, color: _kKeyUmbraOpacity),
const BoxShadow(offset: const Offset(0.0, 16.0), blurRadius: 24.0, spreadRadius: 2.0, color: _kKeyPenumbraOpacity),
const BoxShadow(offset: const Offset(0.0, 6.0), blurRadius: 30.0, spreadRadius: 5.0, color: _kAmbientShadowOpacity),
],
24: const <BoxShadow>[
const BoxShadow(offset: const Offset(0.0, 11.0), blurRadius: 15.0, spreadRadius: -7.0, color: _kKeyUmbraOpacity),
const BoxShadow(offset: const Offset(0.0, 24.0), blurRadius: 38.0, spreadRadius: 3.0, color: _kKeyPenumbraOpacity),
const BoxShadow(offset: const Offset(0.0, 9.0), blurRadius: 46.0, spreadRadius: 8.0, color: _kAmbientShadowOpacity),
],
};
......@@ -77,7 +77,7 @@ class _SnackBar extends StatelessComponent {
minHeight: kSnackBarHeight,
maxHeight: kSnackBarHeight,
child: new Material(
level: 2,
elevation: 6,
color: _kSnackBackground,
child: new Container(
margin: const EdgeDims.symmetric(horizontal: _kSideMargins),
......
......@@ -134,8 +134,8 @@ class _RenderSwitch extends RenderToggleable {
// Draw the raised thumb with a shadow
paint.color = thumbColor;
ShadowDrawLooperBuilder builder = new ShadowDrawLooperBuilder();
for (BoxShadow boxShadow in shadows[1])
builder.addShadow(boxShadow.offset, boxShadow.color, boxShadow.blur);
for (BoxShadow boxShadow in elevationToShadow[1])
builder.addShadow(boxShadow.offset, boxShadow.color, boxShadow.blurRadius);
paint.drawLooper = builder.build();
// The thumb contracts slightly during the animation
......
......@@ -18,7 +18,7 @@ class ToolBar extends StatelessComponent {
this.center,
this.right,
this.bottom,
this.level: 2,
this.elevation: 4,
this.backgroundColor,
this.textTheme,
this.padding: EdgeDims.zero
......@@ -28,7 +28,7 @@ class ToolBar extends StatelessComponent {
final Widget center;
final List<Widget> right;
final Widget bottom;
final int level;
final int elevation;
final Color backgroundColor;
final TextTheme textTheme;
final EdgeDims padding;
......@@ -40,7 +40,7 @@ class ToolBar extends StatelessComponent {
center: center,
right: right,
bottom: bottom,
level: level,
elevation: elevation,
backgroundColor: backgroundColor,
textTheme: textTheme,
padding: newPadding
......@@ -95,7 +95,7 @@ class ToolBar extends StatelessComponent {
padding: new EdgeDims.symmetric(horizontal: 8.0),
decoration: new BoxDecoration(
backgroundColor: color,
boxShadow: level == 0 ? null : shadows[2]
boxShadow: elevationToShadow[elevation]
),
child: new DefaultTextStyle(
style: sideStyle,
......
......@@ -8,7 +8,6 @@ import 'dart:ui' as ui;
import 'package:flutter/services.dart';
import 'basic_types.dart';
import 'shadows.dart';
/// An immutable set of offsets in each of the four cardinal directions.
///
......@@ -253,11 +252,13 @@ class Border {
///
/// Note: 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.
class BoxShadow {
const BoxShadow({
this.color,
this.offset,
this.blur
this.blurRadius,
this.spreadRadius: 0.0
});
/// The color of the shadow
......@@ -267,14 +268,20 @@ class BoxShadow {
final Offset offset;
/// The standard deviation of the Gaussian to convolve with the box's shape
final double blur;
final double blurRadius;
/// Returns a new box shadow with its offset and blur scaled by the given factor
final double spreadRadius;
// See SkBlurMask::ConvertRadiusToSigma()
double get _blurSigma => blurRadius * 0.57735 + 0.5;
/// 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,
blur: blur * factor
blurRadius: blurRadius * factor,
spreadRadius: spreadRadius * factor
);
}
......@@ -282,7 +289,7 @@ class BoxShadow {
///
/// 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 blur.
/// offset and a zero blurRadius.
static BoxShadow lerp(BoxShadow a, BoxShadow b, double t) {
if (a == null && b == null)
return null;
......@@ -293,7 +300,8 @@ class BoxShadow {
return new BoxShadow(
color: Color.lerp(a.color, b.color, t),
offset: Offset.lerp(a.offset, b.offset, t),
blur: ui.lerpDouble(a.blur, b.blur, t)
blurRadius: ui.lerpDouble(a.blurRadius, b.blurRadius, t),
spreadRadius: ui.lerpDouble(a.spreadRadius, b.spreadRadius, t)
);
}
......@@ -326,18 +334,20 @@ class BoxShadow {
final BoxShadow typedOther = other;
return color == typedOther.color &&
offset == typedOther.offset &&
blur == typedOther.blur;
blurRadius == typedOther.blurRadius &&
spreadRadius == typedOther.spreadRadius;
}
int get hashCode {
int value = 373;
value = 37 * value + color.hashCode;
value = 37 * value + offset.hashCode;
value = 37 * value + blur.hashCode;
value = 37 * value + blurRadius.hashCode;
value = 37 * value + spreadRadius.hashCode;
return value;
}
String toString() => 'BoxShadow($color, $offset, $blur)';
String toString() => 'BoxShadow($color, $offset, $blurRadius, $spreadRadius)';
}
/// A 2D gradient
......@@ -894,13 +904,6 @@ class BoxPainter {
if (_decoration.backgroundColor != null)
paint.color = _decoration.backgroundColor;
if (_decoration.boxShadow != null) {
var builder = new ShadowDrawLooperBuilder();
for (BoxShadow boxShadow in _decoration.boxShadow)
builder.addShadow(boxShadow.offset, boxShadow.color, boxShadow.blur);
paint.drawLooper = builder.build();
}
if (_decoration.gradient != null)
paint.shader = _decoration.gradient.createShader();
......@@ -937,27 +940,40 @@ class BoxPainter {
return _decoration.borderRadius > shortestSide ? shortestSide : _decoration.borderRadius;
}
void _paintBackgroundColor(ui.Canvas canvas, Rect rect) {
if (_decoration.backgroundColor != null ||
_decoration.boxShadow != null ||
_decoration.gradient != null) {
void _paintBox(ui.Canvas canvas, Rect rect, Paint paint) {
switch (_decoration.shape) {
case Shape.circle:
assert(_decoration.borderRadius == null);
Point center = rect.center;
double radius = rect.shortestSide / 2.0;
canvas.drawCircle(center, radius, _backgroundPaint);
canvas.drawCircle(center, radius, paint);
break;
case Shape.rectangle:
if (_decoration.borderRadius == null) {
canvas.drawRect(rect, _backgroundPaint);
canvas.drawRect(rect, paint);
} else {
double radius = _getEffectiveBorderRadius(rect);
canvas.drawRRect(new ui.RRect.fromRectXY(rect, radius, radius), _backgroundPaint);
canvas.drawRRect(new ui.RRect.fromRectXY(rect, radius, radius), paint);
}
break;
}
}
void _paintShadows(ui.Canvas canvas, Rect rect) {
if (_decoration.boxShadow == null)
return;
for (BoxShadow boxShadow in _decoration.boxShadow) {
final Paint paint = new Paint()
..color = boxShadow.color
..maskFilter = new ui.MaskFilter.blur(ui.BlurStyle.normal, boxShadow._blurSigma);
final Rect bounds = rect.shift(boxShadow.offset).inflate(boxShadow.spreadRadius);
_paintBox(canvas, bounds, paint);
}
}
void _paintBackgroundColor(ui.Canvas canvas, Rect rect) {
if (_decoration.backgroundColor != null || _decoration.gradient != null)
_paintBox(canvas, rect, _backgroundPaint);
}
void _paintBackgroundImage(ui.Canvas canvas, Rect rect) {
......@@ -1071,6 +1087,7 @@ class BoxPainter {
/// Paint the box decoration into the given location on the given canvas
void paint(ui.Canvas canvas, Rect rect) {
_paintShadows(canvas, rect);
_paintBackgroundColor(canvas, rect);
_paintBackgroundImage(canvas, rect);
_paintBorder(canvas, rect);
......
......@@ -12,7 +12,7 @@ void main() {
gradient: new RadialGradient(
center: Point.origin, radius: 500.0,
colors: <Color>[Colors.yellow[500], Colors.blue[500]]),
boxShadow: shadows[3])
boxShadow: elevationToShadow[3])
);
layout(root);
expect(root.size.width, equals(800.0));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment