| // 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)'; |
| } |