gradient.dart 39.3 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

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

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

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

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

23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
/// 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],
38
      (t - stops[index]) / (stops[index + 1] - stops[index]),
39
  )!;
40 41 42
}

_ColorsAndStops _interpolateColorsAndStops(
43 44 45 46 47
  List<Color> aColors,
  List<double> aStops,
  List<Color> bColors,
  List<double> bStops,
  double t,
48 49 50 51 52 53 54 55 56 57
) {
  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>(
58
          (double stop) => Color.lerp(_sample(aColors, aStops, stop), _sample(bColors, bStops, stop), t)!,
59
  ).toList(growable: false);
60
  return _ColorsAndStops(interpolatedColors, interpolatedStops);
61 62
}

63 64 65 66 67 68 69 70
/// 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 {
71 72
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
73 74 75 76 77 78 79 80
  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].
81
  Matrix4? transform(Rect bounds, {TextDirection? textDirection});
82 83 84 85 86
}

/// A [GradientTransform] that rotates the gradient around the center-point of
/// its bounding box.
///
87
/// {@tool snippet}
88 89 90 91 92 93 94 95 96
///
/// 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),
/// );
/// ```
97
/// {@end-tool}
98 99 100 101 102 103 104 105 106 107 108
@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
109
  Matrix4 transform(Rect bounds, {TextDirection? textDirection}) {
110 111 112 113 114 115 116 117 118 119 120 121 122
    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);
  }
}

123 124
/// A 2D gradient.
///
125 126
/// This is an interface that allows [LinearGradient], [RadialGradient], and
/// [SweepGradient] classes to be used interchangeably in [BoxDecoration]s.
127 128 129
///
/// See also:
///
130
///  * [Gradient](dart-ui/Gradient-class.html), the class in the [dart:ui] library.
131
///
132 133
@immutable
abstract class Gradient {
134 135 136 137 138 139 140 141
  /// 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).
142 143 144 145 146 147 148
  ///
  /// 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.
149
  const Gradient({
150
    required this.colors,
151
    this.stops,
152
    this.transform,
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 178
  }) : 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.
179
  final List<double>? stops;
180

181 182 183 184
  /// 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.
185
  final GradientTransform? transform;
186

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

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

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

221
  /// Linearly interpolates from another [Gradient] to `this`.
222 223 224 225 226 227 228 229
  ///
  /// 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].
  ///
230 231 232 233 234 235 236 237 238 239 240 241 242
  /// 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].
  ///
243 244
  /// Instead of calling this directly, use [Gradient.lerp].
  @protected
245
  Gradient? lerpFrom(Gradient? a, double t) {
246 247 248 249 250
    if (a == null)
      return scale(t);
    return null;
  }

251
  /// Linearly interpolates from `this` to another [Gradient].
252 253 254 255 256 257 258 259 260 261
  ///
  /// 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].
  ///
262 263 264 265 266 267 268 269 270 271 272 273
  /// 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].
  ///
274 275
  /// Instead of calling this directly, use [Gradient.lerp].
  @protected
276
  Gradient? lerpTo(Gradient? b, double t) {
277 278 279 280 281
    if (b == null)
      return scale(1.0 - t);
    return null;
  }

282 283 284 285 286 287 288
  /// 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`.
  ///
289
  /// {@macro dart.ui.shadow.lerp}
290
  static Gradient? lerp(Gradient? a, Gradient? b, double t) {
291
    assert(t != null);
292
    Gradient? result;
293 294 295 296
    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
297 298
    if (result != null)
      return result;
299
    if (a == null && b == null)
300
      return null;
301
    assert(a != null && b != null);
302
    return t < 0.5 ? a!.scale(1.0 - (t * 2.0)) : b!.scale((t - 0.5) * 2.0);
303
  }
304

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

/// A 2D linear gradient.
///
312 313 314
/// 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.
315 316 317 318 319 320 321 322
///
/// 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
323 324 325 326
/// 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.
327 328 329 330 331 332 333
///
/// 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].
///
334
/// {@tool dartpad --template=stateless_widget_material}
335 336 337 338
/// This sample draws a picture that looks like vertical window shades by having
/// a [Container] display a [BoxDecoration] with a [LinearGradient].
///
/// ```dart
339 340
///  Widget build(BuildContext context) {
///    return Container(
341 342
///      decoration: const BoxDecoration(
///        gradient: const LinearGradient(
343 344
///          begin: Alignment.topLeft,
///          end: Alignment(0.8, 0.0), // 10% of the width, so there are ten blinds.
345
///          colors: const <Color>[Color(0xffee0000), Color(0xffeeee00)], // red to yellow
346 347 348 349 350
///          tileMode: TileMode.repeated, // repeats the gradient over the canvas
///        ),
///      ),
///    );
///  }
351
/// ```
352
/// {@end-tool}
353 354 355 356 357
///
/// See also:
///
///  * [RadialGradient], which displays a gradient in concentric circles, and
///    has an example which shows a different way to use [Gradient] objects.
358 359
///  * [SweepGradient], which displays a gradient in a sweeping arc around a
///    center point.
360 361 362
///  * [BoxDecoration], which can take a [LinearGradient] in its
///    [BoxDecoration.gradient] property.
class LinearGradient extends Gradient {
363
  /// Creates a linear gradient.
364 365 366 367
  ///
  /// The [colors] argument must not be null. If [stops] is non-null, it must
  /// have the same length as [colors].
  const LinearGradient({
368 369
    this.begin = Alignment.centerLeft,
    this.end = Alignment.centerRight,
370 371
    required List<Color> colors,
    List<double>? stops,
372
    this.tileMode = TileMode.clamp,
373
    GradientTransform? transform,
374 375
  }) : assert(begin != null),
       assert(end != null),
376
       assert(tileMode != null),
377
       super(colors: colors, stops: stops, transform: transform);
378

379 380
  /// The offset at which stop 0.0 of the gradient is placed.
  ///
381
  /// If this is an [Alignment], then it is expressed as a vector from
382 383
  /// 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).
384
  ///
385
  /// For example, a begin offset of (-1.0, 0.0) is half way down the
386
  /// left side of the box.
387
  ///
388
  /// It can also be an [AlignmentDirectional], where the start is the
389 390 391
  /// 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].
392
  final AlignmentGeometry begin;
393

394
  /// The offset at which stop 1.0 of the gradient is placed.
395
  ///
396
  /// If this is an [Alignment], then it is expressed as a vector from
397 398
  /// 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).
399
  ///
400
  /// For example, a begin offset of (1.0, 0.0) is half way down the
401
  /// right side of the box.
402
  ///
403
  /// It can also be an [AlignmentDirectional], where the start is the left in
404 405
  /// left-to-right contexts and the right in right-to-left contexts. If a
  /// text-direction-dependent value is provided here, then the [createShader]
406
  /// method will need to be given a [TextDirection].
407
  final AlignmentGeometry end;
408 409 410 411 412 413

  /// How this gradient should tile the plane beyond in the region before
  /// [begin] and after [end].
  ///
  /// For details, see [TileMode].
  ///
414 415 416
  /// ![](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)
417 418 419
  final TileMode tileMode;

  @override
420
  Shader createShader(Rect rect, { TextDirection? textDirection }) {
421
    return ui.Gradient.linear(
422 423
      begin.resolve(textDirection).withinRect(rect),
      end.resolve(textDirection).withinRect(rect),
424
      colors, _impliedStops(), tileMode, _resolveTransform(rect, textDirection),
425 426 427
    );
  }

428
  /// Returns a new [LinearGradient] with its colors scaled by the given factor.
429
  ///
430 431
  /// 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.
432
  @override
433
  LinearGradient scale(double factor) {
434
    return LinearGradient(
435 436
      begin: begin,
      end: end,
437
      colors: colors.map<Color>((Color color) => Color.lerp(null, color, factor)!).toList(),
438 439 440 441 442
      stops: stops,
      tileMode: tileMode,
    );
  }

443
  @override
444
  Gradient? lerpFrom(Gradient? a, double t) {
445
    if (a == null || (a is LinearGradient))
446
      return LinearGradient.lerp(a as LinearGradient?, this, t);
447 448 449 450
    return super.lerpFrom(a, t);
  }

  @override
451
  Gradient? lerpTo(Gradient? b, double t) {
452
    if (b == null || (b is LinearGradient))
453
      return LinearGradient.lerp(this, b as LinearGradient?, t);
454 455 456
    return super.lerpTo(b, t);
  }

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

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

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

  @override
  String toString() {
519
    return '${objectRuntimeType(this, 'LinearGradient')}($begin, $end, $colors, $stops, $tileMode)';
520 521 522 523 524
  }
}

/// A 2D radial gradient.
///
525 526 527
/// 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.
528
///
529 530 531
/// 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
532
/// can be reused with varying sized boxes without changing the parameters.
533 534
/// (This contrasts with [new ui.Gradient.radial], whose arguments are expressed
/// in logical pixels.)
535
///
536 537 538 539 540 541 542
/// 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
543
/// normal radial gradient).  One important case to avoid is having [focal] and
544 545
/// [center] both resolve to [Offset.zero] when [focalRadius] > 0.0. In such a case,
/// a valid shader cannot be created by the framework.
546 547
///
/// The [colors] are described by a list of [Color] objects. There must be at
548 549 550 551
/// 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.
552 553 554 555 556 557 558
///
/// 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].
///
559
/// {@tool snippet}
560 561 562 563 564
///
/// This function draws a gradient that looks like a sun in a blue sky.
///
/// ```dart
/// void paintSky(Canvas canvas, Rect rect) {
565 566
///   const RadialGradient gradient = RadialGradient(
///     center: Alignment(0.7, -0.6), // near the top right
567
///     radius: 0.2,
568 569 570
///     colors: <Color>[
///       Color(0xFFFFFF00), // yellow sun
///       Color(0xFF0099FF), // blue sky
571
///     ],
572
///     stops: <double>[0.4, 1.0],
573 574
///   );
///   // rect is the area we are painting over
575
///   final Paint paint = Paint()
576 577 578 579
///     ..shader = gradient.createShader(rect);
///   canvas.drawRect(rect, paint);
/// }
/// ```
580
/// {@end-tool}
581 582 583 584 585
///
/// See also:
///
///  * [LinearGradient], which displays a gradient in parallel lines, and has an
///    example which shows a different way to use [Gradient] objects.
586 587
///  * [SweepGradient], which displays a gradient in a sweeping arc around a
///    center point.
588 589 590 591 592 593 594 595 596 597
///  * [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({
598 599
    this.center = Alignment.center,
    this.radius = 0.5,
600 601
    required List<Color> colors,
    List<double>? stops,
602
    this.tileMode = TileMode.clamp,
603
    this.focal,
604
    this.focalRadius = 0.0,
605
    GradientTransform? transform,
606 607
  }) : assert(center != null),
       assert(radius != null),
608
       assert(tileMode != null),
609
       assert(focalRadius != null),
610
       super(colors: colors, stops: stops, transform: transform);
611

612 613
  /// 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.
614
  ///
615
  /// For example, an alignment of (0.0, 0.0) will place the radial
616
  /// gradient in the center of the box.
617
  ///
618
  /// If this is an [Alignment], then it is expressed as a vector from
619 620
  /// 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).
621
  ///
622
  /// It can also be an [AlignmentDirectional], where the start is the left in
623 624
  /// left-to-right contexts and the right in right-to-left contexts. If a
  /// text-direction-dependent value is provided here, then the [createShader]
625
  /// method will need to be given a [TextDirection].
626
  final AlignmentGeometry center;
627 628 629 630 631 632 633 634 635 636 637 638 639 640

  /// 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].
  ///
641 642 643
  /// ![](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)
644
  ///
645 646 647
  /// ![](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)
648 649
  final TileMode tileMode;

650
  /// The focal point of the gradient.  If specified, the gradient will appear
651 652
  /// to be focused along the vector from [center] to focal.
  ///
653
  /// See [center] for a description of how the coordinates are mapped.
654
  ///
655 656 657
  /// 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.
658
  final AlignmentGeometry? focal;
659 660 661 662 663 664

  /// 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
665
  /// will place the 1.0 stop at 100.0 pixels from the [focal] point.
666
  ///
667 668 669 670 671
  /// 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;

672
  @override
673
  Shader createShader(Rect rect, { TextDirection? textDirection }) {
674
    return ui.Gradient.radial(
675
      center.resolve(textDirection).withinRect(rect),
676
      radius * rect.shortestSide,
677
      colors, _impliedStops(), tileMode,
678
      _resolveTransform(rect, textDirection),
679
      focal == null  ? null : focal!.resolve(textDirection).withinRect(rect),
680
      focalRadius * rect.shortestSide,
681 682 683
    );
  }

684 685
  /// Returns a new [RadialGradient] with its colors scaled by the given factor.
  ///
686 687
  /// 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.
688 689
  @override
  RadialGradient scale(double factor) {
690
    return RadialGradient(
691 692
      center: center,
      radius: radius,
693
      colors: colors.map<Color>((Color color) => Color.lerp(null, color, factor)!).toList(),
694 695
      stops: stops,
      tileMode: tileMode,
696
      focal: focal,
697
      focalRadius: focalRadius,
698 699 700 701
    );
  }

  @override
702
  Gradient? lerpFrom(Gradient? a, double t) {
703
    if (a == null || (a is RadialGradient))
704
      return RadialGradient.lerp(a as RadialGradient?, this, t);
705 706 707 708
    return super.lerpFrom(a, t);
  }

  @override
709
  Gradient? lerpTo(Gradient? b, double t) {
710
    if (b == null || (b is RadialGradient))
711
      return RadialGradient.lerp(this, b as RadialGradient?, t);
712 713 714 715 716 717 718 719 720 721
    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].
722
  ///
723
  /// The `t` argument represents a position on the timeline, with 0.0 meaning
724 725 726 727 728 729 730 731 732 733
  /// 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].
734
  static RadialGradient? lerp(RadialGradient? a, RadialGradient? b, double t) {
735
    assert(t != null);
736 737 738
    if (a == null && b == null)
      return null;
    if (a == null)
739
      return b!.scale(t);
740 741
    if (b == null)
      return a.scale(1.0 - t);
742 743 744 745 746 747 748
    final _ColorsAndStops interpolated = _interpolateColorsAndStops(
        a.colors,
        a._impliedStops(),
        b.colors,
        b._impliedStops(),
        t,
    );
749
    return RadialGradient(
750 751
      center: AlignmentGeometry.lerp(a.center, b.center, t)!,
      radius: math.max(0.0, ui.lerpDouble(a.radius, b.radius, t)!),
752 753 754
      colors: interpolated.colors,
      stops: interpolated.stops,
      tileMode: t < 0.5 ? a.tileMode : b.tileMode, // TODO(ianh): interpolate tile mode
755
      focal: AlignmentGeometry.lerp(a.focal, b.focal, t),
756
      focalRadius: math.max(0.0, ui.lerpDouble(a.focalRadius, b.focalRadius, t)!),
757 758 759
    );
  }

760
  @override
761
  bool operator ==(Object other) {
762 763
    if (identical(this, other))
      return true;
764
    if (other.runtimeType != runtimeType)
765
      return false;
766 767 768 769 770 771 772 773
    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;
774 775 776
  }

  @override
777
  int get hashCode => hashValues(center, radius, tileMode, hashList(colors), hashList(stops), focal, focalRadius);
778 779 780

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

/// 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].
///
807
/// {@tool snippet}
808 809 810 811
///
/// This sample draws a different color in each quadrant.
///
/// ```dart
812
/// Container(
813
///   decoration: const BoxDecoration(
814
///     gradient: SweepGradient(
815 816 817
///       center: FractionalOffset.center,
///       startAngle: 0.0,
///       endAngle: math.pi * 2,
818
///       colors: <Color>[
819 820 821 822 823
///         Color(0xFF4285F4), // blue
///         Color(0xFF34A853), // green
///         Color(0xFFFBBC05), // yellow
///         Color(0xFFEA4335), // red
///         Color(0xFF4285F4), // blue again to seamlessly transition to the start
824
///       ],
825
///       stops: <double>[0.0, 0.25, 0.5, 0.75, 1.0],
826 827 828 829 830 831
///     ),
///   )
/// )
/// ```
/// {@end-tool}
///
832
/// {@tool snippet}
833 834 835 836 837 838
///
/// This sample takes the above gradient and rotates it by `math.pi/4` radians,
/// i.e. 45 degrees.
///
/// ```dart
/// Container(
839
///   decoration: const BoxDecoration(
840 841 842 843
///     gradient: SweepGradient(
///       center: FractionalOffset.center,
///       startAngle: 0.0,
///       endAngle: math.pi * 2,
844
///       colors: <Color>[
845 846 847 848 849 850
///         Color(0xFF4285F4), // blue
///         Color(0xFF34A853), // green
///         Color(0xFFFBBC05), // yellow
///         Color(0xFFEA4335), // red
///         Color(0xFF4285F4), // blue again to seamlessly transition to the start
///       ],
851
///       stops: <double>[0.0, 0.25, 0.5, 0.75, 1.0],
852 853
///       transform: GradientRotation(math.pi/4),
///     ),
854
///   ),
855
/// )
856
/// ```
857
/// {@end-tool}
858 859 860 861 862 863 864 865 866 867 868 869 870 871 872
///
/// 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({
873 874 875
    this.center = Alignment.center,
    this.startAngle = 0.0,
    this.endAngle = math.pi * 2,
876 877
    required List<Color> colors,
    List<double>? stops,
878
    this.tileMode = TileMode.clamp,
879
    GradientTransform? transform,
880 881 882 883
  }) : assert(center != null),
       assert(startAngle != null),
       assert(endAngle != null),
       assert(tileMode != null),
884
       super(colors: colors, stops: stops, transform: transform);
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 919 920 921 922

  /// 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
923
  Shader createShader(Rect rect, { TextDirection? textDirection }) {
924
    return ui.Gradient.sweep(
925 926 927 928
      center.resolve(textDirection).withinRect(rect),
      colors, _impliedStops(), tileMode,
      startAngle,
      endAngle,
929
      _resolveTransform(rect, textDirection),
930 931 932 933 934 935 936 937 938
    );
  }

  /// 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) {
939
    return SweepGradient(
940 941 942
      center: center,
      startAngle: startAngle,
      endAngle: endAngle,
943
      colors: colors.map<Color>((Color color) => Color.lerp(null, color, factor)!).toList(),
944 945 946 947 948 949
      stops: stops,
      tileMode: tileMode,
    );
  }

  @override
950
  Gradient? lerpFrom(Gradient? a, double t) {
951
    if (a == null || (a is SweepGradient))
952
      return SweepGradient.lerp(a as SweepGradient?, this, t);
953 954 955 956
    return super.lerpFrom(a, t);
  }

  @override
957
  Gradient? lerpTo(Gradient? b, double t) {
958
    if (b == null || (b is SweepGradient))
959
      return SweepGradient.lerp(this, b as SweepGradient?, t);
960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980
    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].
981
  static SweepGradient? lerp(SweepGradient? a, SweepGradient? b, double t) {
982 983 984 985
    assert(t != null);
    if (a == null && b == null)
      return null;
    if (a == null)
986
      return b!.scale(t);
987 988
    if (b == null)
      return a.scale(1.0 - t);
989 990 991 992 993 994 995
    final _ColorsAndStops interpolated = _interpolateColorsAndStops(
        a.colors,
        a._impliedStops(),
        b.colors,
        b._impliedStops(),
        t,
    );
996
    return SweepGradient(
997 998 999
      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)!),
1000 1001 1002 1003 1004 1005 1006
      colors: interpolated.colors,
      stops: interpolated.stops,
      tileMode: t < 0.5 ? a.tileMode : b.tileMode, // TODO(ianh): interpolate tile mode
    );
  }

  @override
1007
  bool operator ==(Object other) {
1008 1009
    if (identical(this, other))
      return true;
1010
    if (other.runtimeType != runtimeType)
1011
      return false;
1012 1013 1014 1015 1016 1017 1018
    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);
1019 1020 1021 1022 1023 1024 1025
  }

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

  @override
  String toString() {
1026
    return '${objectRuntimeType(this, 'SweepGradient')}($center, $startAngle, $endAngle, $colors, $stops, $tileMode)';
1027 1028
  }
}