Unverified Commit 9b92c919 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Imply stops on LinearGradient and RadialGradient if they're missing (#13987)

parent 77711d4a
...@@ -45,9 +45,58 @@ _ColorsAndStops _interpolateColorsAndStops(List<Color> aColors, List<double> aSt ...@@ -45,9 +45,58 @@ _ColorsAndStops _interpolateColorsAndStops(List<Color> aColors, List<double> aSt
/// encapsulated by this class and its subclasses. /// encapsulated by this class and its subclasses.
@immutable @immutable
abstract class Gradient { abstract class Gradient {
/// Abstract const constructor. This constructor enables subclasses to provide /// Initialize the gradient's colors and stops.
/// const constructors so that they can be used in const expressions. ///
const Gradient(); /// 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).
const Gradient({
@required this.colors,
this.stops,
}) : 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;
List<double> _impliedStops() {
if (stops != null)
return stops;
if (colors.length == 2)
return null;
assert(colors.length >= 2, 'colors list must have at least two colors');
final double separation = 1.0 / (colors.length - 1);
return new List<double>.generate(
colors.length,
(int index) => index * separation,
growable: false,
);
}
/// Creates a [Shader] for this gradient to fill the given rect. /// Creates a [Shader] for this gradient to fill the given rect.
/// ///
...@@ -175,9 +224,10 @@ abstract class Gradient { ...@@ -175,9 +224,10 @@ abstract class Gradient {
/// ui.Gradient.linear], whose arguments are expressed in logical pixels.) /// ui.Gradient.linear], whose arguments are expressed in logical pixels.)
/// ///
/// The [colors] are described by a list of [Color] objects. There must be at /// The [colors] are described by a list of [Color] objects. There must be at
/// least two colors. If there are more than two, a [stops] list must be /// least two colors. The [stops] list, if specified, must have the same length
/// provided. It must have the same length as [colors], and specifies the /// as [colors]. It specifies fractions of the vector from start to end, between
/// position of each color stop between 0.0 and 1.0. /// 0.0 and 1.0, for each color. If it is null, a uniform distribution is
/// assumed.
/// ///
/// The region of the canvas before [begin] and after [end] is colored according /// The region of the canvas before [begin] and after [end] is colored according
/// to [tileMode]. /// to [tileMode].
...@@ -217,13 +267,13 @@ class LinearGradient extends Gradient { ...@@ -217,13 +267,13 @@ class LinearGradient extends Gradient {
const LinearGradient({ const LinearGradient({
this.begin: Alignment.centerLeft, this.begin: Alignment.centerLeft,
this.end: Alignment.centerRight, this.end: Alignment.centerRight,
@required this.colors, @required List<Color> colors,
this.stops, List<double> stops,
this.tileMode: TileMode.clamp, this.tileMode: TileMode.clamp,
}) : assert(begin != null), }) : assert(begin != null),
assert(end != null), assert(end != null),
assert(colors != null), assert(tileMode != null),
assert(tileMode != null); super(colors: colors, stops: stops);
/// The offset at which stop 0.0 of the gradient is placed. /// The offset at which stop 0.0 of the gradient is placed.
/// ///
...@@ -255,32 +305,6 @@ class LinearGradient extends Gradient { ...@@ -255,32 +305,6 @@ class LinearGradient extends Gradient {
/// method will need to be given a [TextDirection]. /// method will need to be given a [TextDirection].
final AlignmentGeometry end; final AlignmentGeometry end;
/// 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]. If
/// [colors] has more than two colors, [stops] must be non-null.
///
/// 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 of the vector from
/// start to end.
///
/// If non-null, this list must have the same length as [colors]. If
/// [colors] has more than two colors, [stops] must be non-null.
///
/// 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.
final List<double> stops;
/// How this gradient should tile the plane beyond in the region before /// How this gradient should tile the plane beyond in the region before
/// [begin] and after [end]. /// [begin] and after [end].
/// ///
...@@ -296,7 +320,7 @@ class LinearGradient extends Gradient { ...@@ -296,7 +320,7 @@ class LinearGradient extends Gradient {
return new ui.Gradient.linear( return new ui.Gradient.linear(
begin.resolve(textDirection).withinRect(rect), begin.resolve(textDirection).withinRect(rect),
end.resolve(textDirection).withinRect(rect), end.resolve(textDirection).withinRect(rect),
colors, stops, tileMode, colors, _impliedStops(), tileMode,
); );
} }
...@@ -421,9 +445,10 @@ class LinearGradient extends Gradient { ...@@ -421,9 +445,10 @@ class LinearGradient extends Gradient {
/// pixels.) /// pixels.)
/// ///
/// The [colors] are described by a list of [Color] objects. There must be at /// The [colors] are described by a list of [Color] objects. There must be at
/// least two colors. If there are more than two, a [stops] list must be /// least two colors. The [stops] list, if specified, must have the same length
/// provided. It must have the same length as [colors], and specifies the /// as [colors]. It specifies fractions of the radius between 0.0 and 1.0,
/// position of each color stop between 0.0 and 1.0. /// giving concentric rings for each color stop. If it is null, a uniform
/// distribution is assumed.
/// ///
/// The region of the canvas beyond [radius] from the [center] is colored /// The region of the canvas beyond [radius] from the [center] is colored
/// according to [tileMode]. /// according to [tileMode].
...@@ -469,13 +494,13 @@ class RadialGradient extends Gradient { ...@@ -469,13 +494,13 @@ class RadialGradient extends Gradient {
const RadialGradient({ const RadialGradient({
this.center: Alignment.center, this.center: Alignment.center,
this.radius: 0.5, this.radius: 0.5,
@required this.colors, @required List<Color> colors,
this.stops, List<double> stops,
this.tileMode: TileMode.clamp, this.tileMode: TileMode.clamp,
}) : assert(center != null), }) : assert(center != null),
assert(radius != null), assert(radius != null),
assert(colors != null), assert(tileMode != null),
assert(tileMode != null); super(colors: colors, stops: stops);
/// The center of the gradient, as an offset into the (-1.0, -1.0) x (1.0, 1.0) /// 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. /// square describing the gradient which will be mapped onto the paint box.
...@@ -501,34 +526,6 @@ class RadialGradient extends Gradient { ...@@ -501,34 +526,6 @@ class RadialGradient extends Gradient {
/// will place the 1.0 stop at 100.0 pixels from the [center]. /// will place the 1.0 stop at 100.0 pixels from the [center].
final double radius; final double radius;
/// 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]. If
/// [colors] has more than two colors, [stops] must be non-null.
///
/// 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 concentric rings.
///
/// The rings are centered at [center] and have a radius equal to the value of
/// the stop times [radius].
///
/// If non-null, this list must have the same length as [colors]. If
/// [colors] has more than two colors, [stops] must be non-null.
///
/// 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.
final List<double> stops;
/// How this gradient should tile the plane beyond the outer ring at [radius] /// How this gradient should tile the plane beyond the outer ring at [radius]
/// pixels from the [center]. /// pixels from the [center].
/// ///
...@@ -544,7 +541,7 @@ class RadialGradient extends Gradient { ...@@ -544,7 +541,7 @@ class RadialGradient extends Gradient {
return new ui.Gradient.radial( return new ui.Gradient.radial(
center.resolve(textDirection).withinRect(rect), center.resolve(textDirection).withinRect(rect),
radius * rect.shortestSide, radius * rect.shortestSide,
colors, stops, tileMode, colors, _impliedStops(), tileMode,
); );
} }
......
...@@ -237,4 +237,42 @@ void main() { ...@@ -237,4 +237,42 @@ void main() {
expect(Gradient.lerp(testGradient1, testGradient2, 1.0), testGradient2); expect(Gradient.lerp(testGradient1, testGradient2, 1.0), testGradient2);
expect(Gradient.lerp(testGradient1, testGradient2, 0.5), testGradient2.scale(0.0)); expect(Gradient.lerp(testGradient1, testGradient2, 0.5), testGradient2.scale(0.0));
}); });
test('Gradients can handle missing stops and report mismatched stops', () {
final LinearGradient test1a = const LinearGradient(
colors: const <Color>[
const Color(0x11111111),
const Color(0x22222222),
const Color(0x33333333),
],
);
final RadialGradient test1b = const RadialGradient(
colors: const <Color>[
const Color(0x11111111),
const Color(0x22222222),
const Color(0x33333333),
],
);
final LinearGradient test2a = const LinearGradient(
colors: const <Color>[
const Color(0x11111111),
const Color(0x22222222),
const Color(0x33333333),
],
stops: const <double>[0.0, 1.0],
);
final RadialGradient test2b = const RadialGradient(
colors: const <Color>[
const Color(0x11111111),
const Color(0x22222222),
const Color(0x33333333),
],
stops: const <double>[0.0, 1.0],
);
final Rect rect = new Rect.fromLTWH(1.0, 2.0, 3.0, 4.0);
expect(test1a.createShader(rect), isNotNull);
expect(test1b.createShader(rect), isNotNull);
expect(() { test2a.createShader(rect); }, throwsArgumentError);
expect(() { test2b.createShader(rect); }, throwsArgumentError);
});
} }
\ No newline at end of file
...@@ -145,8 +145,8 @@ const Matcher hasAGoodToStringDeep = const _HasGoodToStringDeep(); ...@@ -145,8 +145,8 @@ const Matcher hasAGoodToStringDeep = const _HasGoodToStringDeep();
/// See also: /// See also:
/// ///
/// * [throwsAssertionError], to test if a function throws any [AssertionError]. /// * [throwsAssertionError], to test if a function throws any [AssertionError].
/// * [throwsArgumentError], to test if a functions throws an [ArgumentError].
/// * [isFlutterError], to test if any object is a [FlutterError]. /// * [isFlutterError], to test if any object is a [FlutterError].
/// * [isAssertionError], to test if any object is any kind of [AssertionError].
Matcher throwsFlutterError = throwsA(isFlutterError); Matcher throwsFlutterError = throwsA(isFlutterError);
/// A matcher for functions that throw [AssertionError]. /// A matcher for functions that throw [AssertionError].
...@@ -156,7 +156,7 @@ Matcher throwsFlutterError = throwsA(isFlutterError); ...@@ -156,7 +156,7 @@ Matcher throwsFlutterError = throwsA(isFlutterError);
/// See also: /// See also:
/// ///
/// * [throwsFlutterError], to test if a function throws a [FlutterError]. /// * [throwsFlutterError], to test if a function throws a [FlutterError].
/// * [isFlutterError], to test if any object is a [FlutterError]. /// * [throwsArgumentError], to test if a functions throws an [ArgumentError].
/// * [isAssertionError], to test if any object is any kind of [AssertionError]. /// * [isAssertionError], to test if any object is any kind of [AssertionError].
Matcher throwsAssertionError = throwsA(isAssertionError); Matcher throwsAssertionError = throwsA(isAssertionError);
...@@ -167,7 +167,6 @@ Matcher throwsAssertionError = throwsA(isAssertionError); ...@@ -167,7 +167,6 @@ Matcher throwsAssertionError = throwsA(isAssertionError);
/// See also: /// See also:
/// ///
/// * [throwsFlutterError], to test if a function throws a [FlutterError]. /// * [throwsFlutterError], to test if a function throws a [FlutterError].
/// * [throwsAssertionError], to test if a function throws any [AssertionError].
/// * [isAssertionError], to test if any object is any kind of [AssertionError]. /// * [isAssertionError], to test if any object is any kind of [AssertionError].
const Matcher isFlutterError = const isInstanceOf<FlutterError>(); const Matcher isFlutterError = const isInstanceOf<FlutterError>();
...@@ -177,7 +176,6 @@ const Matcher isFlutterError = const isInstanceOf<FlutterError>(); ...@@ -177,7 +176,6 @@ const Matcher isFlutterError = const isInstanceOf<FlutterError>();
/// ///
/// See also: /// See also:
/// ///
/// * [throwsFlutterError], to test if a function throws a [FlutterError].
/// * [throwsAssertionError], to test if a function throws any [AssertionError]. /// * [throwsAssertionError], to test if a function throws any [AssertionError].
/// * [isFlutterError], to test if any object is a [FlutterError]. /// * [isFlutterError], to test if any object is a [FlutterError].
const Matcher isAssertionError = const isInstanceOf<AssertionError>(); const Matcher isAssertionError = const isInstanceOf<AssertionError>();
......
...@@ -277,10 +277,8 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker ...@@ -277,10 +277,8 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
/// ///
/// * [pumpAndSettle], which essentially calls [pump] until there are no /// * [pumpAndSettle], which essentially calls [pump] until there are no
/// scheduled frames. /// scheduled frames.
///
/// * [SchedulerBinding.transientCallbackCount], which is the value on which /// * [SchedulerBinding.transientCallbackCount], which is the value on which
/// this is based. /// this is based.
///
/// * [SchedulerBinding.hasScheduledFrame], which is true whenever a frame is /// * [SchedulerBinding.hasScheduledFrame], which is true whenever a frame is
/// pending. [SchedulerBinding.hasScheduledFrame] is made true when a /// pending. [SchedulerBinding.hasScheduledFrame] is made true when a
/// widget calls [State.setState], even if there are no transient callbacks /// widget calls [State.setState], even if there are no transient callbacks
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment