LinearGradient and RadialGradient RTL (#12204)

This commit is contained in:
Ian Hickson 2017-09-21 14:06:46 -07:00 committed by GitHub
parent 4f6e350b6e
commit daedbc84a2
3 changed files with 215 additions and 100 deletions

View file

@ -25,7 +25,11 @@ abstract class Gradient {
const Gradient();
/// Creates a [Shader] for this gradient to fill the given rect.
Shader createShader(Rect rect);
///
/// If the gradient's configuration is text-direction-dependent, for example
/// it uses [FractionalOffsetDirection] objects instead of [FractionalOffset]
/// objects, then the `textDirection` argument must not be null.
Shader createShader(Rect rect, { TextDirection textDirection });
}
/// A 2D linear gradient.
@ -91,21 +95,37 @@ class LinearGradient extends Gradient {
assert(colors != null),
assert(tileMode != null);
/// The offset from coordinate (0.0,0.0) at which stop 0.0 of the
/// gradient is placed, in a coordinate space that maps the top left
/// of the paint box at (0.0,0.0) and the bottom right at (1.0,1.0).
/// The offset at which stop 0.0 of the gradient is placed.
///
/// If this is a [FractionalOffset], then it is expressed as a vector from
/// coordinate (0.0,0.0), in a coordinate space that maps the top left of the
/// paint box at (0.0,0.0) and the bottom right at (1.0,1.0).
///
/// For example, a begin offset of (0.0,0.5) is half way down the
/// left side of the box.
final FractionalOffset begin;
/// The offset from coordinate (0.0,0.0) at which stop 1.0 of the
/// gradient is placed, in a coordinate space that maps the top left
/// of the paint box at (0.0,0.0) and the bottom right at (1.0,1.0).
///
/// For example, an end offset of (1.0,0.5) is half way down the
/// It can also be a [FractionalOffsetDirectional], in which case it is
/// expressed as a vector from the top start corner, 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 FractionalOffsetGeometry begin;
/// The offset at which stop 1.0 of the gradient is placed.
///
/// If this is a [FractionalOffset], then it is expressed as a vector from
/// coordinate (0.0,0.0), in a coordinate space that maps the top left of the
/// paint box at (0.0,0.0) and the bottom right at (1.0,1.0).
///
/// For example, a begin offset of (1.0,0.5) is half way down the
/// right side of the box.
final FractionalOffset end;
///
/// It can also be a [FractionalOffsetDirectional], in which case it is
/// expressed as a vector from the top start corner, 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 FractionalOffsetGeometry end;
/// The colors the gradient should obtain at each of the stops.
///
@ -144,16 +164,20 @@ class LinearGradient extends Gradient {
final TileMode tileMode;
@override
Shader createShader(Rect rect) {
Shader createShader(Rect rect, { TextDirection textDirection }) {
return new ui.Gradient.linear(
begin.withinRect(rect),
end.withinRect(rect),
begin.resolve(textDirection).withinRect(rect),
end.resolve(textDirection).withinRect(rect),
colors, stops, tileMode,
);
}
/// Returns a new [LinearGradient] with its properties scaled by the given
/// factor.
/// Returns a new [LinearGradient] with its properties (in particular the
/// colors) scaled by the given factor.
///
/// If the factor is 1.0 or greater, then the gradient is returned unmodified.
/// If the factor is 0.0 or less, then the gradient is fully transparent.
/// Values in between scale the opacity of the colors.
LinearGradient scale(double factor) {
return new LinearGradient(
begin: begin,
@ -168,7 +192,7 @@ class LinearGradient extends Gradient {
///
/// If either gradient is null, this function linearly interpolates from a
/// a gradient that matches the other gradient in [begin], [end], [stops] and
/// [tileMode] and with the same [colors] but transparent.
/// [tileMode] and with the same [colors] but transparent (using [scale]).
///
/// If neither gradient is null, they must have the same number of [colors].
static LinearGradient lerp(LinearGradient a, LinearGradient b, double t) {
@ -178,11 +202,7 @@ class LinearGradient extends Gradient {
return b.scale(t);
if (b == null)
return a.scale(1.0 - t);
// Interpolation is only possible when the lengths of colors and stops are
// the same or stops is null for one.
// TODO(xster): lerp unsimilar LinearGradients in the future by scaling
// lists of LinearGradients.
assert(a.colors.length == b.colors.length);
assert(a.colors.length == b.colors.length, 'Cannot interpolate between two gradients with a different number of colors.');
assert(a.stops == null || b.stops == null || a.stops.length == b.stops.length);
final List<Color> interpolatedColors = <Color>[];
for (int i = 0; i < a.colors.length; i += 1)
@ -195,8 +215,8 @@ class LinearGradient extends Gradient {
interpolatedStops = a.stops ?? b.stops;
}
return new LinearGradient(
begin: FractionalOffset.lerp(a.begin, b.begin, t),
end: FractionalOffset.lerp(a.end, b.end, t),
begin: FractionalOffsetGeometry.lerp(a.begin, b.begin, t),
end: FractionalOffsetGeometry.lerp(a.end, b.end, t),
colors: interpolatedColors,
stops: interpolatedStops,
tileMode: t < 0.5 ? a.tileMode : b.tileMode,
@ -240,7 +260,7 @@ class LinearGradient extends Gradient {
@override
String toString() {
return 'LinearGradient($begin, $end, $colors, $stops, $tileMode)';
return '$runtimeType($begin, $end, $colors, $stops, $tileMode)';
}
}
@ -319,7 +339,17 @@ class RadialGradient extends Gradient {
///
/// For example, an offset of (0.5,0.5) will place the radial
/// gradient in the center of the box.
final FractionalOffset center;
///
/// If this is a [FractionalOffset], then it is expressed as a vector from
/// coordinate (0.0,0.0), in a coordinate space that maps the top left of the
/// paint box at (0.0,0.0) and the bottom right at (1.0,1.0).
///
/// It can also be a [FractionalOffsetDirectional], in which case it is
/// expressed as a vector from the top start corner, 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 FractionalOffsetGeometry center;
/// The radius of the gradient, as a fraction of the shortest side
/// of the paint box.
@ -368,11 +398,11 @@ class RadialGradient extends Gradient {
final TileMode tileMode;
@override
Shader createShader(Rect rect) {
Shader createShader(Rect rect, { TextDirection textDirection }) {
return new ui.Gradient.radial(
center.withinRect(rect),
center.resolve(textDirection).withinRect(rect),
radius * rect.shortestSide,
colors, stops, tileMode
colors, stops, tileMode,
);
}
@ -413,6 +443,6 @@ class RadialGradient extends Gradient {
@override
String toString() {
return 'RadialGradient($center, $radius, $colors, $stops, $tileMode)';
return '$runtimeType($center, $radius, $colors, $stops, $tileMode)';
}
}

View file

@ -112,74 +112,4 @@ void main() {
test('BoxShadow toString test', () {
expect(const BoxShadow(blurRadius: 4.0).toString(), equals('BoxShadow(Color(0xff000000), Offset(0.0, 0.0), 4.0, 0.0)'));
});
test('LinearGradient scale test', () {
final LinearGradient testGradient = const LinearGradient(
begin: FractionalOffset.bottomRight,
end: const FractionalOffset(0.7, 1.0),
colors: const <Color>[
const Color(0x00FFFFFF),
const Color(0x11777777),
const Color(0x44444444),
],
);
final LinearGradient actual = LinearGradient.lerp(null, testGradient, 0.25);
expect(actual, const LinearGradient(
begin: FractionalOffset.bottomRight,
end: const FractionalOffset(0.7, 1.0),
colors: const <Color>[
const Color(0x00FFFFFF),
const Color(0x04777777),
const Color(0x11444444),
],
));
});
test('LinearGradient lerp test', () {
final LinearGradient testGradient1 = const LinearGradient(
begin: FractionalOffset.topLeft,
end: FractionalOffset.bottomLeft,
colors: const <Color>[
const Color(0x33333333),
const Color(0x66666666),
],
);
final LinearGradient testGradient2 = const LinearGradient(
begin: FractionalOffset.topRight,
end: FractionalOffset.topLeft,
colors: const <Color>[
const Color(0x44444444),
const Color(0x88888888),
],
);
final LinearGradient actual =
LinearGradient.lerp(testGradient1, testGradient2, 0.5);
expect(actual, const LinearGradient(
begin: const FractionalOffset(0.5, 0.0),
end: const FractionalOffset(0.0, 0.5),
colors: const <Color>[
const Color(0x3B3B3B3B),
const Color(0x77777777),
],
));
});
test('LinearGradient toString', () {
expect(
const LinearGradient(
begin: FractionalOffset.topLeft,
end: FractionalOffset.bottomLeft,
colors: const <Color>[
const Color(0x33333333),
const Color(0x66666666),
],
).toString(),
equals(
'LinearGradient(FractionalOffset.topLeft, FractionalOffset.bottomLeft, [Color(0x33333333), Color(0x66666666)], null, TileMode.clamp)',
),
);
});
}

View file

@ -0,0 +1,155 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/painting.dart';
void main() {
test('LinearGradient scale test', () {
final LinearGradient testGradient = const LinearGradient(
begin: FractionalOffset.bottomRight,
end: const FractionalOffset(0.7, 1.0),
colors: const <Color>[
const Color(0x00FFFFFF),
const Color(0x11777777),
const Color(0x44444444),
],
);
final LinearGradient actual = LinearGradient.lerp(null, testGradient, 0.25);
expect(actual, const LinearGradient(
begin: FractionalOffset.bottomRight,
end: const FractionalOffset(0.7, 1.0),
colors: const <Color>[
const Color(0x00FFFFFF),
const Color(0x04777777),
const Color(0x11444444),
],
));
});
test('LinearGradient lerp test', () {
final LinearGradient testGradient1 = const LinearGradient(
begin: FractionalOffset.topLeft,
end: FractionalOffset.bottomLeft,
colors: const <Color>[
const Color(0x33333333),
const Color(0x66666666),
],
);
final LinearGradient testGradient2 = const LinearGradient(
begin: FractionalOffset.topRight,
end: FractionalOffset.topLeft,
colors: const <Color>[
const Color(0x44444444),
const Color(0x88888888),
],
);
final LinearGradient actual = LinearGradient.lerp(testGradient1, testGradient2, 0.5);
expect(actual, const LinearGradient(
begin: const FractionalOffset(0.5, 0.0),
end: const FractionalOffset(0.0, 0.5),
colors: const <Color>[
const Color(0x3B3B3B3B),
const Color(0x77777777),
],
));
});
test('LinearGradient toString', () {
expect(
const LinearGradient(
begin: FractionalOffset.topLeft,
end: FractionalOffset.bottomLeft,
colors: const <Color>[
const Color(0x33333333),
const Color(0x66666666),
],
).toString(),
equals(
'LinearGradient(FractionalOffset.topLeft, FractionalOffset.bottomLeft, [Color(0x33333333), Color(0x66666666)], null, TileMode.clamp)',
),
);
});
test('LinearGradient with FractionalOffsetDirectional', () {
expect(
() {
return const LinearGradient(
begin: FractionalOffsetDirectional.topStart,
colors: const <Color>[ const Color(0xFFFFFFFF), const Color(0xFFFFFFFF) ]
).createShader(new Rect.fromLTWH(0.0, 0.0, 100.0, 100.0));
},
throwsAssertionError,
);
expect(
() {
return const LinearGradient(
begin: FractionalOffsetDirectional.topStart,
colors: const <Color>[ const Color(0xFFFFFFFF), const Color(0xFFFFFFFF) ]
).createShader(new Rect.fromLTWH(0.0, 0.0, 100.0, 100.0), textDirection: TextDirection.rtl);
},
returnsNormally,
);
expect(
() {
return const LinearGradient(
begin: FractionalOffsetDirectional.topStart,
colors: const <Color>[ const Color(0xFFFFFFFF), const Color(0xFFFFFFFF) ]
).createShader(new Rect.fromLTWH(0.0, 0.0, 100.0, 100.0), textDirection: TextDirection.ltr);
},
returnsNormally,
);
expect(
() {
return const LinearGradient(
begin: FractionalOffset.topLeft,
colors: const <Color>[ const Color(0xFFFFFFFF), const Color(0xFFFFFFFF) ]
).createShader(new Rect.fromLTWH(0.0, 0.0, 100.0, 100.0));
},
returnsNormally,
);
});
test('RadialGradient with FractionalOffsetDirectional', () {
expect(
() {
return const RadialGradient(
center: FractionalOffsetDirectional.topStart,
colors: const <Color>[ const Color(0xFFFFFFFF), const Color(0xFFFFFFFF) ]
).createShader(new Rect.fromLTWH(0.0, 0.0, 100.0, 100.0));
},
throwsAssertionError,
);
expect(
() {
return const RadialGradient(
center: FractionalOffsetDirectional.topStart,
colors: const <Color>[ const Color(0xFFFFFFFF), const Color(0xFFFFFFFF) ]
).createShader(new Rect.fromLTWH(0.0, 0.0, 100.0, 100.0), textDirection: TextDirection.rtl);
},
returnsNormally,
);
expect(
() {
return const RadialGradient(
center: FractionalOffsetDirectional.topStart,
colors: const <Color>[ const Color(0xFFFFFFFF), const Color(0xFFFFFFFF) ]
).createShader(new Rect.fromLTWH(0.0, 0.0, 100.0, 100.0), textDirection: TextDirection.ltr);
},
returnsNormally,
);
expect(
() {
return const RadialGradient(
center: FractionalOffset.topLeft,
colors: const <Color>[ const Color(0xFFFFFFFF), const Color(0xFFFFFFFF) ]
).createShader(new Rect.fromLTWH(0.0, 0.0, 100.0, 100.0));
},
returnsNormally,
);
});
}