gradient.dart 39 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'dart:collection';
6
import 'dart:math' as math;
7
import 'dart:typed_data';
8 9 10
import 'dart:ui' as ui show Gradient, lerpDouble;

import 'package:flutter/foundation.dart';
11
import 'package:vector_math/vector_math_64.dart';
12

13
import 'alignment.dart';
14 15
import 'basic_types.dart';

16 17 18 19 20 21
class _ColorsAndStops {
  _ColorsAndStops(this.colors, this.stops);
  final List<Color> colors;
  final List<double> stops;
}

22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
/// Calculate the color at position [t] of the gradient defined by [colors] and [stops].
Color _sample(List<Color> colors, List<double> stops, double t) {
  assert(colors != null);
  assert(colors.isNotEmpty);
  assert(stops != null);
  assert(stops.isNotEmpty);
  assert(t != null);
  if (t <= stops.first)
    return colors.first;
  if (t >= stops.last)
    return colors.last;
  final int index = stops.lastIndexWhere((double s) => s <= t);
  assert(index != -1);
  return Color.lerp(
      colors[index], colors[index + 1],
37
      (t - stops[index]) / (stops[index + 1] - stops[index]),
38 39 40 41
  );
}

_ColorsAndStops _interpolateColorsAndStops(
42 43 44 45 46
  List<Color> aColors,
  List<double> aStops,
  List<Color> bColors,
  List<double> bStops,
  double t,
47 48 49 50 51 52 53 54 55 56 57 58
) {
  assert(aColors.length >= 2);
  assert(bColors.length >= 2);
  assert(aStops.length == aColors.length);
  assert(bStops.length == bColors.length);
  final SplayTreeSet<double> stops = SplayTreeSet<double>()
    ..addAll(aStops)
    ..addAll(bStops);
  final List<double> interpolatedStops = stops.toList(growable: false);
  final List<Color> interpolatedColors = interpolatedStops.map<Color>(
          (double stop) => Color.lerp(_sample(aColors, aStops, stop), _sample(bColors, bStops, stop), t)
  ).toList(growable: false);
59
  return _ColorsAndStops(interpolatedColors, interpolatedStops);
60 61
}

62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
/// Base class for transforming gradient shaders without applying the same
/// transform to the entire canvas.
///
/// For example, a [SweepGradient] normally starts its gradation at 3 o'clock
/// and draws clockwise. To have the sweep appear to start at 6 o'clock, supply
/// a [GradientRotation] of `pi/4` radians (i.e. 45 degrees).
@immutable
abstract class GradientTransform {
  /// A const constructor so that subclasses may be const.
  const GradientTransform();

  /// When a [Gradient] creates its [Shader], it will call this method to
  /// determine what transform to apply to the shader for the given [Rect] and
  /// [TextDirection].
  ///
  /// Implementers may return null from this method, which achieves the same
  /// final effect as returning [Matrix4.identity].
  Matrix4 transform(Rect bounds, {TextDirection textDirection});
}

/// A [GradientTransform] that rotates the gradient around the center-point of
/// its bounding box.
///
85
/// {@tool snippet}
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
///
/// This sample would rotate a sweep gradient by a quarter turn clockwise:
///
/// ```dart
/// const SweepGradient gradient = SweepGradient(
///   colors: <Color>[Color(0xFFFFFFFF), Color(0xFF009900)],
///   transform: GradientRotation(math.pi/4),
/// );
/// ```
@immutable
class GradientRotation extends GradientTransform {
  /// Constructs a [GradientRotation] for the specified angle.
  ///
  /// The angle is in radians in the clockwise direction.
  const GradientRotation(this.radians);

  /// The angle of rotation in radians in the clockwise direction.
  final double radians;

  @override
  Matrix4 transform(Rect bounds, {TextDirection textDirection}) {
    assert(bounds != null);
    final double sinRadians = math.sin(radians);
    final double oneMinusCosRadians = 1 - math.cos(radians);
    final Offset center = bounds.center;
    final double originX = sinRadians * center.dy + oneMinusCosRadians * center.dx;
    final double originY = -sinRadians * center.dx + oneMinusCosRadians * center.dy;

    return Matrix4.identity()
      ..translate(originX, originY)
      ..rotateZ(radians);
  }
}

120 121
/// A 2D gradient.
///
122 123
/// This is an interface that allows [LinearGradient], [RadialGradient], and
/// [SweepGradient] classes to be used interchangeably in [BoxDecoration]s.
124 125 126
///
/// See also:
///
127
///  * [Gradient](dart-ui/Gradient-class.html), the class in the [dart:ui] library.
128
///
129 130
@immutable
abstract class Gradient {
131 132 133 134 135 136 137 138
  /// Initialize the gradient's colors and stops.
  ///
  /// The [colors] argument must not be null, and must have at least two colors
  /// (the length is not verified until the [createShader] method is called).
  ///
  /// If specified, the [stops] argument must have the same number of entries as
  /// [colors] (this is also not verified until the [createShader] method is
  /// called).
139 140 141 142 143 144 145
  ///
  /// The [transform] argument can be applied to transform _only_ the gradient,
  /// without rotating the canvas itself or other geometry on the canvas. For
  /// example, a `GradientRotation(math.pi/4)` will result in a [SweepGradient]
  /// that starts from a position of 6 o'clock instead of 3 o'clock, assuming
  /// no other rotation or perspective transformations have been applied to the
  /// [Canvas]. If null, no transformation is applied.
146 147 148
  const Gradient({
    @required this.colors,
    this.stops,
149
    this.transform,
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
  }) : assert(colors != null);

  /// The colors the gradient should obtain at each of the stops.
  ///
  /// If [stops] is non-null, this list must have the same length as [stops].
  ///
  /// This list must have at least two colors in it (otherwise, it's not a
  /// gradient!).
  final List<Color> colors;

  /// A list of values from 0.0 to 1.0 that denote fractions along the gradient.
  ///
  /// If non-null, this list must have the same length as [colors].
  ///
  /// If the first value is not 0.0, then a stop with position 0.0 and a color
  /// equal to the first color in [colors] is implied.
  ///
  /// If the last value is not 1.0, then a stop with position 1.0 and a color
  /// equal to the last color in [colors] is implied.
  ///
  /// The values in the [stops] list must be in ascending order. If a value in
  /// the [stops] list is less than an earlier value in the list, then its value
  /// is assumed to equal the previous value.
  ///
  /// If stops is null, then a set of uniformly distributed stops is implied,
  /// with the first stop at 0.0 and the last stop at 1.0.
  final List<double> stops;

178 179 180 181 182 183
  /// The transform, if any, to apply to the gradient.
  ///
  /// This transform is in addition to any other transformations applied to the
  /// canvas, but does not add any transformations to the canvas.
  final GradientTransform transform;

184 185 186 187 188
  List<double> _impliedStops() {
    if (stops != null)
      return stops;
    assert(colors.length >= 2, 'colors list must have at least two colors');
    final double separation = 1.0 / (colors.length - 1);
189
    return List<double>.generate(
190 191 192 193 194
      colors.length,
      (int index) => index * separation,
      growable: false,
    );
  }
195 196

  /// Creates a [Shader] for this gradient to fill the given rect.
197 198
  ///
  /// If the gradient's configuration is text-direction-dependent, for example
199
  /// it uses [AlignmentDirectional] objects instead of [Alignment]
200
  /// objects, then the `textDirection` argument must not be null.
201 202 203
  ///
  /// The shader's transform will be resolved from the [transform] of this
  /// gradient.
204
  Shader createShader(Rect rect, { TextDirection textDirection });
205 206 207 208 209 210 211 212 213 214 215 216

  /// Returns a new gradient with its properties scaled by the given factor.
  ///
  /// A factor of 0.0 (or less) should result in a variant of the gradient that
  /// is invisible; any two factors epsilon apart should be unnoticeably
  /// different from each other at first glance. From this it follows that
  /// scaling a gradient with values from 1.0 to 0.0 over time should cause the
  /// gradient to smoothly disappear.
  ///
  /// Typically this is the same as interpolating from null (with [lerp]).
  Gradient scale(double factor);

217
  /// Linearly interpolates from another [Gradient] to `this`.
218 219 220 221 222 223 224 225
  ///
  /// When implementing this method in subclasses, return null if this class
  /// cannot interpolate from `a`. In that case, [lerp] will try `a`'s [lerpTo]
  /// method instead.
  ///
  /// If `a` is null, this must not return null. The base class implements this
  /// by deferring to [scale].
  ///
226 227 228 229 230 231 232 233 234 235 236 237 238
  /// 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 `this` (or something equivalent to `this`), and values in
  /// between meaning that the interpolation is at the relevant point on the
  /// timeline between `a` and `this`. 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].
  ///
239 240 241 242 243 244 245 246
  /// Instead of calling this directly, use [Gradient.lerp].
  @protected
  Gradient lerpFrom(Gradient a, double t) {
    if (a == null)
      return scale(t);
    return null;
  }

247
  /// Linearly interpolates from `this` to another [Gradient].
248 249 250 251 252 253 254 255 256 257
  ///
  /// This is called if `b`'s [lerpTo] did not know how to handle this class.
  ///
  /// When implementing this method in subclasses, return null if this class
  /// cannot interpolate from `b`. In that case, [lerp] will apply a default
  /// behavior instead.
  ///
  /// If `b` is null, this must not return null. The base class implements this
  /// by deferring to [scale].
  ///
258 259 260 261 262 263 264 265 266 267 268 269
  /// The `t` argument represents position on the timeline, with 0.0 meaning
  /// that the interpolation has not started, returning `this` (or something
  /// equivalent to `this`), 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 `this` 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].
  ///
270 271 272 273 274 275 276 277
  /// Instead of calling this directly, use [Gradient.lerp].
  @protected
  Gradient lerpTo(Gradient b, double t) {
    if (b == null)
      return scale(1.0 - t);
    return null;
  }

278 279 280 281 282 283 284
  /// Linearly interpolates between two [Gradient]s.
  ///
  /// This defers to `b`'s [lerpTo] function if `b` is not null. If `b` is
  /// null or if its [lerpTo] returns null, it uses `a`'s [lerpFrom]
  /// function instead. If both return null, it returns `a` before `t == 0.5`
  /// and `b` after `t == 0.5`.
  ///
285
  /// {@macro dart.ui.shadow.lerp}
286 287
  static Gradient lerp(Gradient a, Gradient b, double t) {
    assert(t != null);
288
    Gradient result;
289 290 291 292
    if (b != null)
      result = b.lerpFrom(a, t); // if a is null, this must return non-null
    if (result == null && a != null)
      result = a.lerpTo(b, t); // if b is null, this must return non-null
293 294
    if (result != null)
      return result;
295
    if (a == null && b == null)
296
      return null;
297 298
    assert(a != null && b != null);
    return t < 0.5 ? a.scale(1.0 - (t * 2.0)) : b.scale((t - 0.5) * 2.0);
299
  }
300 301 302 303

  Float64List _resolveTransform(Rect bounds, TextDirection textDirection) {
    return transform?.transform(bounds, textDirection: textDirection)?.storage;
  }
304 305 306 307
}

/// A 2D linear gradient.
///
308 309 310
/// This class is used by [BoxDecoration] to represent linear gradients. This
/// abstracts out the arguments to the [new ui.Gradient.linear] constructor from
/// the `dart:ui` library.
311 312 313 314 315 316 317 318
///
/// A gradient has two anchor points, [begin] and [end]. The [begin] point
/// corresponds to 0.0, and the [end] point corresponds to 1.0. These points are
/// expressed in fractions, so that the same gradient can be reused with varying
/// sized boxes without changing the parameters. (This contrasts with [new
/// ui.Gradient.linear], whose arguments are expressed in logical pixels.)
///
/// The [colors] are described by a list of [Color] objects. There must be at
319 320 321 322
/// least two colors. The [stops] list, if specified, must have the same length
/// as [colors]. It specifies fractions of the vector from start to end, between
/// 0.0 and 1.0, for each color. If it is null, a uniform distribution is
/// assumed.
323 324 325 326 327 328 329
///
/// The region of the canvas before [begin] and after [end] is colored according
/// to [tileMode].
///
/// Typically this class is used with [BoxDecoration], which does the painting.
/// To use a [LinearGradient] to paint on a canvas directly, see [createShader].
///
330
/// {@tool snippet}
331 332 333 334 335
///
/// This sample draws a picture that looks like vertical window shades by having
/// a [Container] display a [BoxDecoration] with a [LinearGradient].
///
/// ```dart
336 337 338
/// Container(
///   decoration: BoxDecoration(
///     gradient: LinearGradient(
339
///       begin: Alignment.topLeft,
340
///       end: Alignment(0.8, 0.0), // 10% of the width, so there are ten blinds.
341 342 343 344 345 346
///       colors: [const Color(0xFFFFFFEE), const Color(0xFF999999)], // whitish to gray
///       tileMode: TileMode.repeated, // repeats the gradient over the canvas
///     ),
///   ),
/// )
/// ```
347
/// {@end-tool}
348 349 350 351 352
///
/// See also:
///
///  * [RadialGradient], which displays a gradient in concentric circles, and
///    has an example which shows a different way to use [Gradient] objects.
353 354
///  * [SweepGradient], which displays a gradient in a sweeping arc around a
///    center point.
355 356 357
///  * [BoxDecoration], which can take a [LinearGradient] in its
///    [BoxDecoration.gradient] property.
class LinearGradient extends Gradient {
358
  /// Creates a linear gradient.
359 360 361 362
  ///
  /// The [colors] argument must not be null. If [stops] is non-null, it must
  /// have the same length as [colors].
  const LinearGradient({
363 364
    this.begin = Alignment.centerLeft,
    this.end = Alignment.centerRight,
365 366
    @required List<Color> colors,
    List<double> stops,
367
    this.tileMode = TileMode.clamp,
368
    GradientTransform transform,
369 370
  }) : assert(begin != null),
       assert(end != null),
371
       assert(tileMode != null),
372
       super(colors: colors, stops: stops, transform: transform);
373

374 375
  /// The offset at which stop 0.0 of the gradient is placed.
  ///
376
  /// If this is an [Alignment], then it is expressed as a vector from
377 378
  /// coordinate (0.0, 0.0), in a coordinate space that maps the center of the
  /// paint box at (0.0, 0.0) and the bottom right at (1.0, 1.0).
379
  ///
380
  /// For example, a begin offset of (-1.0, 0.0) is half way down the
381
  /// left side of the box.
382
  ///
383
  /// It can also be an [AlignmentDirectional], where the start is the
384 385 386
  /// left in left-to-right contexts and the right in right-to-left contexts. If
  /// a text-direction-dependent value is provided here, then the [createShader]
  /// method will need to be given a [TextDirection].
387
  final AlignmentGeometry begin;
388

389
  /// The offset at which stop 1.0 of the gradient is placed.
390
  ///
391
  /// If this is an [Alignment], then it is expressed as a vector from
392 393
  /// coordinate (0.0, 0.0), in a coordinate space that maps the center of the
  /// paint box at (0.0, 0.0) and the bottom right at (1.0, 1.0).
394
  ///
395
  /// For example, a begin offset of (1.0, 0.0) is half way down the
396
  /// right side of the box.
397
  ///
398
  /// It can also be an [AlignmentDirectional], where the start is the left in
399 400
  /// left-to-right contexts and the right in right-to-left contexts. If a
  /// text-direction-dependent value is provided here, then the [createShader]
401
  /// method will need to be given a [TextDirection].
402
  final AlignmentGeometry end;
403 404 405 406 407 408

  /// How this gradient should tile the plane beyond in the region before
  /// [begin] and after [end].
  ///
  /// For details, see [TileMode].
  ///
409 410 411
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_clamp_linear.png)
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_mirror_linear.png)
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_repeated_linear.png)
412 413 414
  final TileMode tileMode;

  @override
415
  Shader createShader(Rect rect, { TextDirection textDirection }) {
416
    return ui.Gradient.linear(
417 418
      begin.resolve(textDirection).withinRect(rect),
      end.resolve(textDirection).withinRect(rect),
419
      colors, _impliedStops(), tileMode, _resolveTransform(rect, textDirection),
420 421 422
    );
  }

423
  /// Returns a new [LinearGradient] with its colors scaled by the given factor.
424
  ///
425 426
  /// Since the alpha component of the Color is what is scaled, a factor
  /// of 0.0 or less results in a gradient that is fully transparent.
427
  @override
428
  LinearGradient scale(double factor) {
429
    return LinearGradient(
430 431 432 433 434 435 436 437
      begin: begin,
      end: end,
      colors: colors.map<Color>((Color color) => Color.lerp(null, color, factor)).toList(),
      stops: stops,
      tileMode: tileMode,
    );
  }

438 439
  @override
  Gradient lerpFrom(Gradient a, double t) {
440
    if (a == null || (a is LinearGradient))
441
      return LinearGradient.lerp(a as LinearGradient, this, t);
442 443 444 445 446
    return super.lerpFrom(a, t);
  }

  @override
  Gradient lerpTo(Gradient b, double t) {
447
    if (b == null || (b is LinearGradient))
448
      return LinearGradient.lerp(this, b as LinearGradient, t);
449 450 451
    return super.lerpTo(b, t);
  }

452 453 454 455
  /// Linearly interpolate between two [LinearGradient]s.
  ///
  /// If either gradient is null, this function linearly interpolates from a
  /// a gradient that matches the other gradient in [begin], [end], [stops] and
456
  /// [tileMode] and with the same [colors] but transparent (using [scale]).
457 458
  ///
  /// If neither gradient is null, they must have the same number of [colors].
459
  ///
460
  /// The `t` argument represents a position on the timeline, with 0.0 meaning
461 462 463 464 465 466 467 468 469 470
  /// 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].
471
  static LinearGradient lerp(LinearGradient a, LinearGradient b, double t) {
472
    assert(t != null);
473 474 475 476 477 478
    if (a == null && b == null)
      return null;
    if (a == null)
      return b.scale(t);
    if (b == null)
      return a.scale(1.0 - t);
479 480 481 482 483 484 485
    final _ColorsAndStops interpolated = _interpolateColorsAndStops(
        a.colors,
        a._impliedStops(),
        b.colors,
        b._impliedStops(),
        t,
    );
486
    return LinearGradient(
487 488
      begin: AlignmentGeometry.lerp(a.begin, b.begin, t),
      end: AlignmentGeometry.lerp(a.end, b.end, t),
489 490 491
      colors: interpolated.colors,
      stops: interpolated.stops,
      tileMode: t < 0.5 ? a.tileMode : b.tileMode, // TODO(ianh): interpolate tile mode
492 493 494 495
    );
  }

  @override
496
  bool operator ==(Object other) {
497 498
    if (identical(this, other))
      return true;
499
    if (other.runtimeType != runtimeType)
500
      return false;
501 502 503 504 505 506
    return other is LinearGradient
        && other.begin == begin
        && other.end == end
        && other.tileMode == tileMode
        && listEquals<Color>(other.colors, colors)
        && listEquals<double>(other.stops, stops);
507 508 509 510 511 512 513
  }

  @override
  int get hashCode => hashValues(begin, end, tileMode, hashList(colors), hashList(stops));

  @override
  String toString() {
514
    return '${objectRuntimeType(this, 'LinearGradient')}($begin, $end, $colors, $stops, $tileMode)';
515 516 517 518 519
  }
}

/// A 2D radial gradient.
///
520 521 522
/// This class is used by [BoxDecoration] to represent radial gradients. This
/// abstracts out the arguments to the [new ui.Gradient.radial] constructor from
/// the `dart:ui` library.
523
///
524 525 526
/// A normal radial gradient has a [center] and a [radius]. The [center] point
/// corresponds to 0.0, and the ring at [radius] from the center corresponds
/// to 1.0. These lengths are expressed in fractions, so that the same gradient
527
/// can be reused with varying sized boxes without changing the parameters.
528 529
/// (This contrasts with [new ui.Gradient.radial], whose arguments are expressed
/// in logical pixels.)
530
///
531 532 533 534 535 536 537
/// It is also possible to create a two-point (or focal pointed) radial gradient
/// (which is sometimes referred to as a two point conic gradient, but is not the
/// same as a CSS conic gradient which corresponds to a [SweepGradient]). A [focal]
/// point and [focalRadius] can be specified similarly to [center] and [radius],
/// which will make the rendered gradient appear to be pointed or directed in the
/// direction of the [focal] point. This is only important if [focal] and [center]
/// are not equal or [focalRadius] > 0.0 (as this case is visually identical to a
538
/// normal radial gradient).  One important case to avoid is having [focal] and
539 540
/// [center] both resolve to [Offset.zero] when [focalRadius] > 0.0. In such a case,
/// a valid shader cannot be created by the framework.
541 542
///
/// The [colors] are described by a list of [Color] objects. There must be at
543 544 545 546
/// least two colors. The [stops] list, if specified, must have the same length
/// as [colors]. It specifies fractions of the radius between 0.0 and 1.0,
/// giving concentric rings for each color stop. If it is null, a uniform
/// distribution is assumed.
547 548 549 550 551 552 553
///
/// The region of the canvas beyond [radius] from the [center] is colored
/// according to [tileMode].
///
/// Typically this class is used with [BoxDecoration], which does the painting.
/// To use a [RadialGradient] to paint on a canvas directly, see [createShader].
///
554
/// {@tool snippet}
555 556 557 558 559
///
/// This function draws a gradient that looks like a sun in a blue sky.
///
/// ```dart
/// void paintSky(Canvas canvas, Rect rect) {
560
///   var gradient = RadialGradient(
561
///     center: const Alignment(0.7, -0.6), // near the top right
562 563 564 565 566 567 568 569
///     radius: 0.2,
///     colors: [
///       const Color(0xFFFFFF00), // yellow sun
///       const Color(0xFF0099FF), // blue sky
///     ],
///     stops: [0.4, 1.0],
///   );
///   // rect is the area we are painting over
570
///   var paint = Paint()
571 572 573 574
///     ..shader = gradient.createShader(rect);
///   canvas.drawRect(rect, paint);
/// }
/// ```
575
/// {@end-tool}
576 577 578 579 580
///
/// See also:
///
///  * [LinearGradient], which displays a gradient in parallel lines, and has an
///    example which shows a different way to use [Gradient] objects.
581 582
///  * [SweepGradient], which displays a gradient in a sweeping arc around a
///    center point.
583 584 585 586 587 588 589 590 591 592
///  * [BoxDecoration], which can take a [RadialGradient] in its
///    [BoxDecoration.gradient] property.
///  * [CustomPainter], which shows how to use the above sample code in a custom
///    painter.
class RadialGradient extends Gradient {
  /// Creates a radial gradient.
  ///
  /// The [colors] argument must not be null. If [stops] is non-null, it must
  /// have the same length as [colors].
  const RadialGradient({
593 594
    this.center = Alignment.center,
    this.radius = 0.5,
595 596
    @required List<Color> colors,
    List<double> stops,
597
    this.tileMode = TileMode.clamp,
598
    this.focal,
599
    this.focalRadius = 0.0,
600
    GradientTransform transform,
601 602
  }) : assert(center != null),
       assert(radius != null),
603
       assert(tileMode != null),
604
       assert(focalRadius != null),
605
       super(colors: colors, stops: stops, transform: transform);
606

607 608
  /// The center of the gradient, as an offset into the (-1.0, -1.0) x (1.0, 1.0)
  /// square describing the gradient which will be mapped onto the paint box.
609
  ///
610
  /// For example, an alignment of (0.0, 0.0) will place the radial
611
  /// gradient in the center of the box.
612
  ///
613
  /// If this is an [Alignment], then it is expressed as a vector from
614 615
  /// coordinate (0.0, 0.0), in a coordinate space that maps the center of the
  /// paint box at (0.0, 0.0) and the bottom right at (1.0, 1.0).
616
  ///
617
  /// It can also be an [AlignmentDirectional], where the start is the left in
618 619
  /// left-to-right contexts and the right in right-to-left contexts. If a
  /// text-direction-dependent value is provided here, then the [createShader]
620
  /// method will need to be given a [TextDirection].
621
  final AlignmentGeometry center;
622 623 624 625 626 627 628 629 630 631 632 633 634 635

  /// The radius of the gradient, as a fraction of the shortest side
  /// of the paint box.
  ///
  /// For example, if a radial gradient is painted on a box that is
  /// 100.0 pixels wide and 200.0 pixels tall, then a radius of 1.0
  /// will place the 1.0 stop at 100.0 pixels from the [center].
  final double radius;

  /// How this gradient should tile the plane beyond the outer ring at [radius]
  /// pixels from the [center].
  ///
  /// For details, see [TileMode].
  ///
636 637 638
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_clamp_radial.png)
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_mirror_radial.png)
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_repeated_radial.png)
639
  ///
640 641 642
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_clamp_radialWithFocal.png)
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_mirror_radialWithFocal.png)
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_repeated_radialWithFocal.png)
643 644
  final TileMode tileMode;

645
  /// The focal point of the gradient.  If specified, the gradient will appear
646 647
  /// to be focused along the vector from [center] to focal.
  ///
648
  /// See [center] for a description of how the coordinates are mapped.
649
  ///
650 651 652 653 654 655 656 657 658 659 660
  /// If this value is specified and [focalRadius] > 0.0, care should be taken
  /// to ensure that either this value or [center] will not both resolve to
  /// [Offset.zero], which would fail to create a valid gradient.
  final AlignmentGeometry focal;

  /// The radius of the focal point of gradient, as a fraction of the shortest
  /// side of the paint box.
  ///
  /// For example, if a radial gradient is painted on a box that is
  /// 100.0 pixels wide and 200.0 pixels tall, then a radius of 1.0
  /// will place the 1.0 stop at 100.0 pixels from the [focus].
661
  ///
662 663 664 665 666
  /// If this value is specified and is greater than 0.0, either [focal] or
  /// [center] must not resolve to [Offset.zero], which would fail to create
  /// a valid gradient.
  final double focalRadius;

667
  @override
668
  Shader createShader(Rect rect, { TextDirection textDirection }) {
669
    return ui.Gradient.radial(
670
      center.resolve(textDirection).withinRect(rect),
671
      radius * rect.shortestSide,
672
      colors, _impliedStops(), tileMode,
673
      _resolveTransform(rect, textDirection),
674 675
      focal == null  ? null : focal.resolve(textDirection).withinRect(rect),
      focalRadius * rect.shortestSide,
676 677 678
    );
  }

679 680
  /// Returns a new [RadialGradient] with its colors scaled by the given factor.
  ///
681 682
  /// Since the alpha component of the Color is what is scaled, a factor
  /// of 0.0 or less results in a gradient that is fully transparent.
683 684
  @override
  RadialGradient scale(double factor) {
685
    return RadialGradient(
686 687 688 689 690
      center: center,
      radius: radius,
      colors: colors.map<Color>((Color color) => Color.lerp(null, color, factor)).toList(),
      stops: stops,
      tileMode: tileMode,
691
      focal: focal,
692
      focalRadius: focalRadius,
693 694 695 696 697
    );
  }

  @override
  Gradient lerpFrom(Gradient a, double t) {
698
    if (a == null || (a is RadialGradient))
699
      return RadialGradient.lerp(a as RadialGradient, this, t);
700 701 702 703 704
    return super.lerpFrom(a, t);
  }

  @override
  Gradient lerpTo(Gradient b, double t) {
705
    if (b == null || (b is RadialGradient))
706
      return RadialGradient.lerp(this, b as RadialGradient, t);
707 708 709 710 711 712 713 714 715 716
    return super.lerpTo(b, t);
  }

  /// Linearly interpolate between two [RadialGradient]s.
  ///
  /// If either gradient is null, this function linearly interpolates from a
  /// a gradient that matches the other gradient in [center], [radius], [stops] and
  /// [tileMode] and with the same [colors] but transparent (using [scale]).
  ///
  /// If neither gradient is null, they must have the same number of [colors].
717
  ///
718
  /// The `t` argument represents a position on the timeline, with 0.0 meaning
719 720 721 722 723 724 725 726 727 728
  /// 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].
729
  static RadialGradient lerp(RadialGradient a, RadialGradient b, double t) {
730
    assert(t != null);
731 732 733 734 735 736
    if (a == null && b == null)
      return null;
    if (a == null)
      return b.scale(t);
    if (b == null)
      return a.scale(1.0 - t);
737 738 739 740 741 742 743
    final _ColorsAndStops interpolated = _interpolateColorsAndStops(
        a.colors,
        a._impliedStops(),
        b.colors,
        b._impliedStops(),
        t,
    );
744
    return RadialGradient(
745 746 747 748 749
      center: AlignmentGeometry.lerp(a.center, b.center, t),
      radius: math.max(0.0, ui.lerpDouble(a.radius, b.radius, t)),
      colors: interpolated.colors,
      stops: interpolated.stops,
      tileMode: t < 0.5 ? a.tileMode : b.tileMode, // TODO(ianh): interpolate tile mode
750 751
      focal: AlignmentGeometry.lerp(a.focal, b.focal, t),
      focalRadius: math.max(0.0, ui.lerpDouble(a.focalRadius, b.focalRadius, t)),
752 753 754
    );
  }

755
  @override
756
  bool operator ==(Object other) {
757 758
    if (identical(this, other))
      return true;
759
    if (other.runtimeType != runtimeType)
760
      return false;
761 762 763 764 765 766 767 768
    return other is RadialGradient
        && other.center == center
        && other.radius == radius
        && other.tileMode == tileMode
        && listEquals<Color>(other.colors, colors)
        && listEquals<double>(other.stops, stops)
        && other.focal == focal
        && other.focalRadius == focalRadius;
769 770 771
  }

  @override
772
  int get hashCode => hashValues(center, radius, tileMode, hashList(colors), hashList(stops), focal, focalRadius);
773 774 775

  @override
  String toString() {
776
    return '${objectRuntimeType(this, 'RadialGradient')}($center, $radius, $colors, $stops, $tileMode, $focal, $focalRadius)';
777 778
  }
}
779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801

/// A 2D sweep gradient.
///
/// This class is used by [BoxDecoration] to represent sweep gradients. This
/// abstracts out the arguments to the [new ui.Gradient.sweep] constructor from
/// the `dart:ui` library.
///
/// A gradient has a [center], a [startAngle], and an [endAngle]. The [startAngle]
/// corresponds to 0.0, and the [endAngle] corresponds to 1.0. These angles are
/// expressed in radians.
///
/// The [colors] are described by a list of [Color] objects. There must be at
/// least two colors. The [stops] list, if specified, must have the same length
/// as [colors]. It specifies fractions of the vector from start to end, between
/// 0.0 and 1.0, for each color. If it is null, a uniform distribution is
/// assumed.
///
/// The region of the canvas before [startAngle] and after [endAngle] is colored
/// according to [tileMode].
///
/// Typically this class is used with [BoxDecoration], which does the painting.
/// To use a [SweepGradient] to paint on a canvas directly, see [createShader].
///
802
/// {@tool snippet}
803 804 805 806
///
/// This sample draws a different color in each quadrant.
///
/// ```dart
807 808 809
/// Container(
///   decoration: BoxDecoration(
///     gradient: SweepGradient(
810 811 812 813
///       center: FractionalOffset.center,
///       startAngle: 0.0,
///       endAngle: math.pi * 2,
///       colors: const <Color>[
814 815 816 817 818
///         Color(0xFF4285F4), // blue
///         Color(0xFF34A853), // green
///         Color(0xFFFBBC05), // yellow
///         Color(0xFFEA4335), // red
///         Color(0xFF4285F4), // blue again to seamlessly transition to the start
819 820
///       ],
///       stops: const <double>[0.0, 0.25, 0.5, 0.75, 1.0],
821 822 823 824 825 826
///     ),
///   )
/// )
/// ```
/// {@end-tool}
///
827
/// {@tool snippet}
828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848
///
/// This sample takes the above gradient and rotates it by `math.pi/4` radians,
/// i.e. 45 degrees.
///
/// ```dart
/// Container(
///   decoration: BoxDecoration(
///     gradient: SweepGradient(
///       center: FractionalOffset.center,
///       startAngle: 0.0,
///       endAngle: math.pi * 2,
///       colors: const <Color>[
///         Color(0xFF4285F4), // blue
///         Color(0xFF34A853), // green
///         Color(0xFFFBBC05), // yellow
///         Color(0xFFEA4335), // red
///         Color(0xFF4285F4), // blue again to seamlessly transition to the start
///       ],
///       stops: const <double>[0.0, 0.25, 0.5, 0.75, 1.0],
///       transform: GradientRotation(math.pi/4),
///     ),
849
///   ),
850
/// )
851
/// ```
852
/// {@end-tool}
853 854 855 856 857 858 859 860 861 862 863 864 865 866 867
///
/// See also:
///
///  * [LinearGradient], which displays a gradient in parallel lines, and has an
///    example which shows a different way to use [Gradient] objects.
///  * [RadialGradient], which displays a gradient in concentric circles, and
///    has an example which shows a different way to use [Gradient] objects.
///  * [BoxDecoration], which can take a [SweepGradient] in its
///    [BoxDecoration.gradient] property.
class SweepGradient extends Gradient {
  /// Creates a sweep gradient.
  ///
  /// The [colors] argument must not be null. If [stops] is non-null, it must
  /// have the same length as [colors].
  const SweepGradient({
868 869 870
    this.center = Alignment.center,
    this.startAngle = 0.0,
    this.endAngle = math.pi * 2,
871 872
    @required List<Color> colors,
    List<double> stops,
873
    this.tileMode = TileMode.clamp,
874
    GradientTransform transform,
875 876 877 878
  }) : assert(center != null),
       assert(startAngle != null),
       assert(endAngle != null),
       assert(tileMode != null),
879
       super(colors: colors, stops: stops, transform: transform);
880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918

  /// The center of the gradient, as an offset into the (-1.0, -1.0) x (1.0, 1.0)
  /// square describing the gradient which will be mapped onto the paint box.
  ///
  /// For example, an alignment of (0.0, 0.0) will place the sweep
  /// gradient in the center of the box.
  ///
  /// If this is an [Alignment], then it is expressed as a vector from
  /// coordinate (0.0, 0.0), in a coordinate space that maps the center of the
  /// paint box at (0.0, 0.0) and the bottom right at (1.0, 1.0).
  ///
  /// It can also be an [AlignmentDirectional], where the start is the left in
  /// left-to-right contexts and the right in right-to-left contexts. If a
  /// text-direction-dependent value is provided here, then the [createShader]
  /// method will need to be given a [TextDirection].
  final AlignmentGeometry center;

  /// The angle in radians at which stop 0.0 of the gradient is placed.
  ///
  /// Defaults to 0.0.
  final double startAngle;

  /// The angle in radians at which stop 1.0 of the gradient is placed.
  ///
  /// Defaults to math.pi * 2.
  final double endAngle;

  /// How this gradient should tile the plane beyond in the region before
  /// [startAngle] and after [endAngle].
  ///
  /// For details, see [TileMode].
  ///
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_clamp_sweep.png)
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_mirror_sweep.png)
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_repeated_sweep.png)
  final TileMode tileMode;

  @override
  Shader createShader(Rect rect, { TextDirection textDirection }) {
919
    return ui.Gradient.sweep(
920 921 922 923
      center.resolve(textDirection).withinRect(rect),
      colors, _impliedStops(), tileMode,
      startAngle,
      endAngle,
924
      _resolveTransform(rect, textDirection),
925 926 927 928 929 930 931 932 933
    );
  }

  /// Returns a new [SweepGradient] with its colors scaled by the given factor.
  ///
  /// Since the alpha component of the Color is what is scaled, a factor
  /// of 0.0 or less results in a gradient that is fully transparent.
  @override
  SweepGradient scale(double factor) {
934
    return SweepGradient(
935 936 937 938 939 940 941 942 943 944 945
      center: center,
      startAngle: startAngle,
      endAngle: endAngle,
      colors: colors.map<Color>((Color color) => Color.lerp(null, color, factor)).toList(),
      stops: stops,
      tileMode: tileMode,
    );
  }

  @override
  Gradient lerpFrom(Gradient a, double t) {
946
    if (a == null || (a is SweepGradient))
947
      return SweepGradient.lerp(a as SweepGradient, this, t);
948 949 950 951 952
    return super.lerpFrom(a, t);
  }

  @override
  Gradient lerpTo(Gradient b, double t) {
953
    if (b == null || (b is SweepGradient))
954
      return SweepGradient.lerp(this, b as SweepGradient, t);
955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983
    return super.lerpTo(b, t);
  }

  /// Linearly interpolate between two [SweepGradient]s.
  ///
  /// If either gradient is null, then the non-null gradient is returned with
  /// its color scaled in the same way as the [scale] function.
  ///
  /// If neither gradient is null, they must have the same number of [colors].
  ///
  /// The `t` argument represents a 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 SweepGradient lerp(SweepGradient a, SweepGradient 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);
984 985 986 987 988 989 990
    final _ColorsAndStops interpolated = _interpolateColorsAndStops(
        a.colors,
        a._impliedStops(),
        b.colors,
        b._impliedStops(),
        t,
    );
991
    return SweepGradient(
992 993 994 995 996 997 998 999 1000 1001
      center: AlignmentGeometry.lerp(a.center, b.center, t),
      startAngle: math.max(0.0, ui.lerpDouble(a.startAngle, b.startAngle, t)),
      endAngle: math.max(0.0, ui.lerpDouble(a.endAngle, b.endAngle, t)),
      colors: interpolated.colors,
      stops: interpolated.stops,
      tileMode: t < 0.5 ? a.tileMode : b.tileMode, // TODO(ianh): interpolate tile mode
    );
  }

  @override
1002
  bool operator ==(Object other) {
1003 1004
    if (identical(this, other))
      return true;
1005
    if (other.runtimeType != runtimeType)
1006
      return false;
1007 1008 1009 1010 1011 1012 1013
    return other is SweepGradient
        && other.center == center
        && other.startAngle == startAngle
        && other.endAngle == endAngle
        && other.tileMode == tileMode
        && listEquals<Color>(other.colors, colors)
        && listEquals<double>(other.stops, stops);
1014 1015 1016 1017 1018 1019 1020
  }

  @override
  int get hashCode => hashValues(center, startAngle, endAngle, tileMode, hashList(colors), hashList(stops));

  @override
  String toString() {
1021
    return '${objectRuntimeType(this, 'SweepGradient')}($center, $startAngle, $endAngle, $colors, $stops, $tileMode)';
1022 1023
  }
}