Commit e1b721f0 authored by Hans Muller's avatar Hans Muller

Merge pull request #451 from HansMuller/shadows

Update Material 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.
parents fd6bc856 d9153a13
......@@ -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,29 +940,42 @@ 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) {
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);
break;
case Shape.rectangle:
if (_decoration.borderRadius == null) {
canvas.drawRect(rect, _backgroundPaint);
} else {
double radius = _getEffectiveBorderRadius(rect);
canvas.drawRRect(new ui.RRect.fromRectXY(rect, radius, radius), _backgroundPaint);
}
break;
}
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, paint);
break;
case Shape.rectangle:
if (_decoration.borderRadius == null) {
canvas.drawRect(rect, paint);
} else {
double radius = _getEffectiveBorderRadius(rect);
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) {
final BackgroundImage backgroundImage = _decoration.backgroundImage;
if (backgroundImage == null)
......@@ -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