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