New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Gradient transform #42484
Gradient transform #42484
Changes from 14 commits
8329cf6
3844496
a32c949
d522884
b345595
cf2e0a3
db13dc8
f2db848
db8df6b
13d7f99
6060c6b
7662328
3c964f7
122e5aa
326acfa
36b0cf8
b13c116
4b66a31
cbde192
cd6e736
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
7efcec3e8b0bbb6748a992b23a0a89300aa323c7 | ||
9a2854e5a94f563b1452cbc688b4ff8f7e746991 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,9 +4,11 @@ | |
|
||
import 'dart:collection'; | ||
import 'dart:math' as math; | ||
import 'dart:typed_data'; | ||
import 'dart:ui' as ui show Gradient, lerpDouble; | ||
|
||
import 'package:flutter/foundation.dart'; | ||
import 'package:vector_math/vector_math_64.dart'; | ||
|
||
import 'alignment.dart'; | ||
import 'basic_types.dart'; | ||
|
@@ -57,6 +59,63 @@ _ColorsAndStops _interpolateColorsAndStops( | |
return _ColorsAndStops(interpolatedColors, interpolatedStops); | ||
} | ||
|
||
/// 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 `0.785398` radians (i.e. `45` degrees). | ||
@immutable | ||
abstract class GradientTransform { | ||
/// A const constructor that allows subclasses to be const. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there's a boilerplate paragraph we use for this kind of thing. |
||
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. | ||
/// | ||
/// For example, the following would rotate a sweep gradient by a quarter turn | ||
/// clockwise: | ||
/// | ||
/// ```dart | ||
/// SweepGradient( | ||
/// colors: colors, | ||
/// transform: GradientRotation(0.785398), | ||
/// ); | ||
/// ``` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should use the sample code logic for this |
||
@immutable | ||
class GradientRotation extends GradientTransform { | ||
/// Constructs a `GradientRotation` for the specified angle. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use |
||
/// | ||
/// The angle is in radians in the clockwise direction. | ||
const GradientRotation(this.radians); | ||
|
||
/// The angle of rotation. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs more detail, e.g. direction of rotation (clockwise, counter-clockwise), the units (though it's pretty obvious from the name). |
||
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); | ||
} | ||
} | ||
|
||
/// A 2D gradient. | ||
/// | ||
/// This is an interface that allows [LinearGradient], [RadialGradient], and | ||
|
@@ -76,9 +135,17 @@ abstract class Gradient { | |
/// 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). | ||
/// | ||
/// 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(0.785398)` will result in a [SweepGradient] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. here and elsewhere, rather than 0.785398 we should say pi/4 |
||
/// 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. | ||
const Gradient({ | ||
@required this.colors, | ||
this.stops, | ||
this.transform, | ||
}) : assert(colors != null); | ||
|
||
/// The colors the gradient should obtain at each of the stops. | ||
|
@@ -107,6 +174,12 @@ abstract class Gradient { | |
/// with the first stop at 0.0 and the last stop at 1.0. | ||
final List<double> stops; | ||
|
||
/// 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; | ||
|
||
List<double> _impliedStops() { | ||
if (stops != null) | ||
return stops; | ||
|
@@ -124,6 +197,9 @@ abstract class Gradient { | |
/// If the gradient's configuration is text-direction-dependent, for example | ||
/// it uses [AlignmentDirectional] objects instead of [Alignment] | ||
/// objects, then the `textDirection` argument must not be null. | ||
/// | ||
/// The shader's transform will be resolved from the [transform] of this | ||
/// gradient. | ||
Shader createShader(Rect rect, { TextDirection textDirection }); | ||
|
||
/// Returns a new gradient with its properties scaled by the given factor. | ||
|
@@ -220,6 +296,10 @@ abstract class Gradient { | |
assert(a != null && b != null); | ||
return t < 0.5 ? a.scale(1.0 - (t * 2.0)) : b.scale((t - 0.5) * 2.0); | ||
} | ||
|
||
Float64List _resolveTransform(Rect bounds, TextDirection textDirection) { | ||
return transform?.transform(bounds, textDirection: textDirection)?.storage; | ||
} | ||
} | ||
|
||
/// A 2D linear gradient. | ||
|
@@ -284,10 +364,11 @@ class LinearGradient extends Gradient { | |
@required List<Color> colors, | ||
List<double> stops, | ||
this.tileMode = TileMode.clamp, | ||
GradientTransform transform, | ||
}) : assert(begin != null), | ||
assert(end != null), | ||
assert(tileMode != null), | ||
super(colors: colors, stops: stops); | ||
super(colors: colors, stops: stops, transform: transform); | ||
|
||
/// The offset at which stop 0.0 of the gradient is placed. | ||
/// | ||
|
@@ -334,7 +415,7 @@ class LinearGradient extends Gradient { | |
return ui.Gradient.linear( | ||
begin.resolve(textDirection).withinRect(rect), | ||
end.resolve(textDirection).withinRect(rect), | ||
colors, _impliedStops(), tileMode, | ||
colors, _impliedStops(), tileMode, _resolveTransform(rect, textDirection), | ||
); | ||
} | ||
|
||
|
@@ -533,11 +614,12 @@ class RadialGradient extends Gradient { | |
this.tileMode = TileMode.clamp, | ||
this.focal, | ||
this.focalRadius = 0.0, | ||
GradientTransform transform, | ||
}) : assert(center != null), | ||
assert(radius != null), | ||
assert(tileMode != null), | ||
assert(focalRadius != null), | ||
super(colors: colors, stops: stops); | ||
super(colors: colors, stops: stops, transform: transform); | ||
|
||
/// 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. | ||
|
@@ -605,7 +687,7 @@ class RadialGradient extends Gradient { | |
center.resolve(textDirection).withinRect(rect), | ||
radius * rect.shortestSide, | ||
colors, _impliedStops(), tileMode, | ||
null, // transform | ||
_resolveTransform(rect, textDirection), | ||
focal == null ? null : focal.resolve(textDirection).withinRect(rect), | ||
focalRadius * rect.shortestSide, | ||
); | ||
|
@@ -771,9 +853,36 @@ class RadialGradient extends Gradient { | |
/// Color(0xFF4285F4), // blue again to seamlessly transition to the start | ||
/// ], | ||
/// stops: const <double>[0.0, 0.25, 0.5, 0.75, 1.0], | ||
/// ), | ||
/// ), | ||
/// ) | ||
/// ) | ||
/// ``` | ||
/// {@end-tool} | ||
/// | ||
/// {@tool sample} | ||
/// | ||
/// This sample takes the above gradient and rotates it by 0.785398 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(0.785398), | ||
/// ), | ||
/// ), | ||
/// ) | ||
/// ) | ||
/// ``` | ||
/// {@end-tool} | ||
/// | ||
|
@@ -797,11 +906,12 @@ class SweepGradient extends Gradient { | |
@required List<Color> colors, | ||
List<double> stops, | ||
this.tileMode = TileMode.clamp, | ||
GradientTransform transform, | ||
}) : assert(center != null), | ||
assert(startAngle != null), | ||
assert(endAngle != null), | ||
assert(tileMode != null), | ||
super(colors: colors, stops: stops); | ||
super(colors: colors, stops: stops, transform: transform); | ||
|
||
/// 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. | ||
|
@@ -846,6 +956,7 @@ class SweepGradient extends Gradient { | |
colors, _impliedStops(), tileMode, | ||
startAngle, | ||
endAngle, | ||
_resolveTransform(rect, textDirection), | ||
); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
trivial nit: I think our style is to not use backticks for literals.