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