• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5part of engine;
6
7/// This code is ported from the AngularDart SCSS.
8///
9/// See: https://github.com/dart-lang/angular_components/blob/master/lib/css/material/_shadow.scss
10class ElevationShadow {
11  /// Applies a standard transition style for box-shadow to box-shadow.
12  static void applyShadowTransition(html.CssStyleDeclaration style) {
13    style.transition = 'box-shadow .28s cubic-bezier(.4, 0, .2, 1)';
14  }
15
16  /// Disables box-shadow.
17  static void applyShadowNone(html.CssStyleDeclaration style) {
18    style.boxShadow = 'none';
19  }
20
21  /// Applies a standard shadow to the selected element(s).
22  ///
23  /// This rule is great for things that need a static shadow. If the elevation
24  /// of the shadow needs to be changed dynamically, use [applyShadow].
25  ///
26  /// Valid values: 2, 3, 4, 6, 8, 12, 16, 24
27  static void applyShadowElevation(html.CssStyleDeclaration style,
28      {@required int dp, @required ui.Color color}) {
29    const double keyUmbraOpacity = 0.2;
30    const double keyPenumbraOpacity = 0.14;
31    const double ambientShadowOpacity = 0.12;
32
33    final String rgb = '${color.red}, ${color.green}, ${color.blue}';
34    if (dp == 2) {
35      style.boxShadow = '0 2px 2px 0 rgba($rgb, $keyPenumbraOpacity), '
36          '0 3px 1px -2px rgba($rgb, $ambientShadowOpacity), '
37          '0 1px 5px 0 rgba($rgb, $keyUmbraOpacity)';
38    } else if (dp == 3) {
39      style.boxShadow = '0 3px 4px 0 rgba($rgb, $keyPenumbraOpacity), '
40          '0 3px 3px -2px rgba($rgb, $ambientShadowOpacity), '
41          '0 1px 8px 0 rgba($rgb, $keyUmbraOpacity)';
42    } else if (dp == 4) {
43      style.boxShadow = '0 4px 5px 0 rgba($rgb, $keyPenumbraOpacity), '
44          '0 1px 10px 0 rgba($rgb, $ambientShadowOpacity), '
45          '0 2px 4px -1px rgba($rgb, $keyUmbraOpacity)';
46    } else if (dp == 6) {
47      style.boxShadow = '0 6px 10px 0 rgba($rgb, $keyPenumbraOpacity), '
48          '0 1px 18px 0 rgba($rgb, $ambientShadowOpacity), '
49          '0 3px 5px -1px rgba($rgb, $keyUmbraOpacity)';
50    } else if (dp == 8) {
51      style.boxShadow = '0 8px 10px 1px rgba($rgb, $keyPenumbraOpacity), '
52          '0 3px 14px 2px rgba($rgb, $ambientShadowOpacity), '
53          '0 5px 5px -3px rgba($rgb, $keyUmbraOpacity)';
54    } else if (dp == 12) {
55      style.boxShadow = '0 12px 17px 2px rgba($rgb, $keyPenumbraOpacity), '
56          '0 5px 22px 4px rgba($rgb, $ambientShadowOpacity), '
57          '0 7px 8px -4px rgba($rgb, $keyUmbraOpacity)';
58    } else if (dp == 16) {
59      style.boxShadow = '0 16px 24px 2px rgba($rgb, $keyPenumbraOpacity), '
60          '0  6px 30px 5px rgba($rgb, $ambientShadowOpacity), '
61          '0  8px 10px -5px rgba($rgb, $keyUmbraOpacity)';
62    } else {
63      style.boxShadow = '0 24px 38px 3px rgba($rgb, $keyPenumbraOpacity), '
64          '0  9px 46px 8px rgba($rgb, $ambientShadowOpacity), '
65          '0  11px 15px -7px rgba($rgb, $keyUmbraOpacity)';
66    }
67  }
68
69  /// Applies the shadow styles to the selected element.
70  ///
71  /// Use the attributes below to control the shadow.
72  ///
73  /// - `animated` -- Whether to animate the shadow transition.
74  /// - `elevation` -- Z-elevation of shadow. Valid Values: 1,2,3,4,5
75  static void applyShadow(
76      html.CssStyleDeclaration style, double elevation, ui.Color color) {
77    applyShadowTransition(style);
78
79    if (elevation <= 0.0) {
80      applyShadowNone(style);
81    } else if (elevation <= 1.0) {
82      applyShadowElevation(style, dp: 2, color: color);
83    } else if (elevation <= 2.0) {
84      applyShadowElevation(style, dp: 4, color: color);
85    } else if (elevation <= 3.0) {
86      applyShadowElevation(style, dp: 6, color: color);
87    } else if (elevation <= 4.0) {
88      applyShadowElevation(style, dp: 8, color: color);
89    } else if (elevation <= 5.0) {
90      applyShadowElevation(style, dp: 16, color: color);
91    } else {
92      applyShadowElevation(style, dp: 24, color: color);
93    }
94  }
95
96  static List<CanvasShadow> computeCanvasShadows(
97      double elevation, ui.Color color) {
98    if (elevation <= 0.0) {
99      return const <CanvasShadow>[];
100    } else if (elevation <= 1.0) {
101      return computeShadowElevation(dp: 2, color: color);
102    } else if (elevation <= 2.0) {
103      return computeShadowElevation(dp: 4, color: color);
104    } else if (elevation <= 3.0) {
105      return computeShadowElevation(dp: 6, color: color);
106    } else if (elevation <= 4.0) {
107      return computeShadowElevation(dp: 8, color: color);
108    } else if (elevation <= 5.0) {
109      return computeShadowElevation(dp: 16, color: color);
110    } else {
111      return computeShadowElevation(dp: 24, color: color);
112    }
113  }
114
115  /// Expands rect to include size of shadow.
116  ///
117  /// Computed from shadow elevation offset + spread, blur
118  static ui.Rect computeShadowRect(ui.Rect r, double elevation) {
119    // We are computing this rect by computing the maximum "reach" of the shadow
120    // by summing the computed shadow offset and the blur for the given
121    // elevation.  We are assuming that a blur of '1' corresponds to 1 pixel,
122    // although the web spec says that this is not necessarily the case.
123    // However, it seems to be a good conservative estimate.
124    if (elevation <= 0.0) {
125      return r;
126    } else if (elevation <= 1.0) {
127      return ui.Rect.fromLTRB(
128          r.left - 2.5, r.top - 1.5, r.right + 3, r.bottom + 4);
129    } else if (elevation <= 2.0) {
130      return ui.Rect.fromLTRB(r.left - 5, r.top - 3, r.right + 6, r.bottom + 7);
131    } else if (elevation <= 3.0) {
132      return ui.Rect.fromLTRB(
133          r.left - 9, r.top - 8, r.right + 9, r.bottom + 11);
134    } else if (elevation <= 4.0) {
135      return ui.Rect.fromLTRB(
136          r.left - 10, r.top - 6, r.right + 10, r.bottom + 14);
137    } else if (elevation <= 5.0) {
138      return ui.Rect.fromLTRB(
139          r.left - 15, r.top - 9, r.right + 20, r.bottom + 30);
140    } else {
141      return ui.Rect.fromLTRB(
142          r.left - 23, r.top - 14, r.right + 23, r.bottom + 45);
143    }
144  }
145
146  static List<CanvasShadow> computeShadowElevation(
147      {@required int dp, @required ui.Color color}) {
148    final int red = color.red;
149    final int green = color.green;
150    final int blue = color.blue;
151
152    final ui.Color penumbraColor = ui.Color.fromARGB(36, red, green, blue);
153    final ui.Color ambientShadowColor = ui.Color.fromARGB(31, red, green, blue);
154    final ui.Color umbraColor = ui.Color.fromARGB(51, red, green, blue);
155
156    final List<CanvasShadow> result = <CanvasShadow>[];
157    if (dp == 2) {
158      result.add(CanvasShadow(
159        offsetX: 0.0,
160        offsetY: 2.0,
161        blur: 1.0,
162        spread: 0.0,
163        color: penumbraColor,
164      ));
165
166      result.add(CanvasShadow(
167        offsetX: 0.0,
168        offsetY: 3.0,
169        blur: 0.5,
170        spread: -2.0,
171        color: ambientShadowColor,
172      ));
173
174      result.add(CanvasShadow(
175        offsetX: 0.0,
176        offsetY: 1.0,
177        blur: 2.5,
178        spread: 0.0,
179        color: umbraColor,
180      ));
181    } else if (dp == 3) {
182      result.add(CanvasShadow(
183        offsetX: 0.0,
184        offsetY: 1.5,
185        blur: 4.0,
186        spread: 0.0,
187        color: penumbraColor,
188      ));
189
190      result.add(CanvasShadow(
191        offsetX: 0.0,
192        offsetY: 3.0,
193        blur: 1.5,
194        spread: -2.0,
195        color: ambientShadowColor,
196      ));
197
198      result.add(CanvasShadow(
199        offsetX: 0.0,
200        offsetY: 1.0,
201        blur: 4.0,
202        spread: 0.0,
203        color: umbraColor,
204      ));
205    } else if (dp == 4) {
206      result.add(CanvasShadow(
207        offsetX: 0.0,
208        offsetY: 4.0,
209        blur: 2.5,
210        spread: 0.0,
211        color: penumbraColor,
212      ));
213
214      result.add(CanvasShadow(
215        offsetX: 0.0,
216        offsetY: 1.0,
217        blur: 5.0,
218        spread: 0.0,
219        color: ambientShadowColor,
220      ));
221
222      result.add(CanvasShadow(
223        offsetX: 0.0,
224        offsetY: 2.0,
225        blur: 2.0,
226        spread: -1.0,
227        color: umbraColor,
228      ));
229    } else if (dp == 6) {
230      result.add(CanvasShadow(
231        offsetX: 0.0,
232        offsetY: 6.0,
233        blur: 5.0,
234        spread: 0.0,
235        color: penumbraColor,
236      ));
237
238      result.add(CanvasShadow(
239        offsetX: 0.0,
240        offsetY: 1.0,
241        blur: 9.0,
242        spread: 0.0,
243        color: ambientShadowColor,
244      ));
245
246      result.add(CanvasShadow(
247        offsetX: 0.0,
248        offsetY: 3.0,
249        blur: 2.5,
250        spread: -1.0,
251        color: umbraColor,
252      ));
253    } else if (dp == 8) {
254      result.add(CanvasShadow(
255        offsetX: 0.0,
256        offsetY: 4.0,
257        blur: 10.0,
258        spread: 1.0,
259        color: penumbraColor,
260      ));
261
262      result.add(CanvasShadow(
263        offsetX: 0.0,
264        offsetY: 3.0,
265        blur: 7.0,
266        spread: 2.0,
267        color: ambientShadowColor,
268      ));
269
270      result.add(CanvasShadow(
271        offsetX: 0.0,
272        offsetY: 5.0,
273        blur: 2.5,
274        spread: -3.0,
275        color: umbraColor,
276      ));
277    } else if (dp == 12) {
278      result.add(CanvasShadow(
279        offsetX: 0.0,
280        offsetY: 12.0,
281        blur: 8.5,
282        spread: 2.0,
283        color: penumbraColor,
284      ));
285
286      result.add(CanvasShadow(
287        offsetX: 0.0,
288        offsetY: 5.0,
289        blur: 11.0,
290        spread: 4.0,
291        color: ambientShadowColor,
292      ));
293
294      result.add(CanvasShadow(
295        offsetX: 0.0,
296        offsetY: 7.0,
297        blur: 4.0,
298        spread: -4.0,
299        color: umbraColor,
300      ));
301    } else if (dp == 16) {
302      result.add(CanvasShadow(
303        offsetX: 0.0,
304        offsetY: 16.0,
305        blur: 12.0,
306        spread: 2.0,
307        color: penumbraColor,
308      ));
309
310      result.add(CanvasShadow(
311        offsetX: 0.0,
312        offsetY: 6.0,
313        blur: 15.0,
314        spread: 5.0,
315        color: ambientShadowColor,
316      ));
317
318      result.add(CanvasShadow(
319        offsetX: 0.0,
320        offsetY: 0.0,
321        blur: 5.0,
322        spread: -5.0,
323        color: umbraColor,
324      ));
325    } else {
326      result.add(CanvasShadow(
327        offsetX: 0.0,
328        offsetY: 24.0,
329        blur: 18.0,
330        spread: 3.0,
331        color: penumbraColor,
332      ));
333
334      result.add(CanvasShadow(
335        offsetX: 0.0,
336        offsetY: 9.0,
337        blur: 23.0,
338        spread: 8.0,
339        color: ambientShadowColor,
340      ));
341
342      result.add(CanvasShadow(
343        offsetX: 0.0,
344        offsetY: 11.0,
345        blur: 7.5,
346        spread: -7.0,
347        color: umbraColor,
348      ));
349    }
350    return result;
351  }
352}
353
354class CanvasShadow {
355  CanvasShadow({
356    @required this.offsetX,
357    @required this.offsetY,
358    @required this.blur,
359    @required this.spread,
360    @required this.color,
361  });
362
363  final double offsetX;
364  final double offsetY;
365  final double blur;
366  // TODO(yjbanov): is there a way to implement/emulate spread on Canvas2D?
367  final double spread;
368  final ui.Color color;
369}
370