diff --git a/packages/flutter/lib/src/painting/gradient.dart b/packages/flutter/lib/src/painting/gradient.dart index da5282d59f..bad1dbe188 100644 --- a/packages/flutter/lib/src/painting/gradient.dart +++ b/packages/flutter/lib/src/painting/gradient.dart @@ -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 interpolatedColors = []; 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)'; } } diff --git a/packages/flutter/test/painting/box_painter_test.dart b/packages/flutter/test/painting/box_painter_test.dart index 1900ce9634..364b76840d 100644 --- a/packages/flutter/test/painting/box_painter_test.dart +++ b/packages/flutter/test/painting/box_painter_test.dart @@ -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 [ - 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 [ - 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 [ - const Color(0x33333333), - const Color(0x66666666), - ], - ); - - final LinearGradient testGradient2 = const LinearGradient( - begin: FractionalOffset.topRight, - end: FractionalOffset.topLeft, - colors: const [ - 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 [ - const Color(0x3B3B3B3B), - const Color(0x77777777), - ], - )); - }); - - test('LinearGradient toString', () { - expect( - const LinearGradient( - begin: FractionalOffset.topLeft, - end: FractionalOffset.bottomLeft, - colors: const [ - const Color(0x33333333), - const Color(0x66666666), - ], - ).toString(), - equals( - 'LinearGradient(FractionalOffset.topLeft, FractionalOffset.bottomLeft, [Color(0x33333333), Color(0x66666666)], null, TileMode.clamp)', - ), - ); - }); } diff --git a/packages/flutter/test/painting/gradient_test.dart b/packages/flutter/test/painting/gradient_test.dart new file mode 100644 index 0000000000..c861571aa7 --- /dev/null +++ b/packages/flutter/test/painting/gradient_test.dart @@ -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 [ + 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 [ + 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 [ + const Color(0x33333333), + const Color(0x66666666), + ], + ); + + final LinearGradient testGradient2 = const LinearGradient( + begin: FractionalOffset.topRight, + end: FractionalOffset.topLeft, + colors: const [ + 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 [ + const Color(0x3B3B3B3B), + const Color(0x77777777), + ], + )); + }); + + test('LinearGradient toString', () { + expect( + const LinearGradient( + begin: FractionalOffset.topLeft, + end: FractionalOffset.bottomLeft, + colors: const [ + 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 [ 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 [ 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 [ 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 [ 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 [ 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 [ 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 [ 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 [ const Color(0xFFFFFFFF), const Color(0xFFFFFFFF) ] + ).createShader(new Rect.fromLTWH(0.0, 0.0, 100.0, 100.0)); + }, + returnsNormally, + ); + }); +} \ No newline at end of file