Unverified Commit a18f5e84 authored by Dan Field's avatar Dan Field Committed by GitHub

Expose Text foreground from engine (#18347)

* update tests for TextStyle changes in engine

* roll engine, support Foreground on TextStyle

* roll to TextStyle.foreground

* add tests, update docs, fixes from tests

* add golden tests

* stroke + gradient

* update goldens hash

* Use centered widget

* fix typo

* Disable golden tests until Linux generated files are available

* update goldens
parent c35e484c
......@@ -10,6 +10,9 @@ import 'basic_types.dart';
const String _kDefaultDebugLabel = 'unknown';
const String _kColorForegroundWarning = 'Cannot provide both a color and a foreground\n'
'The color argument is just a shorthand for "foreground: new Paint()..color = color".';
/// An immutable style in which paint text.
///
/// ## Sample code
......@@ -39,7 +42,7 @@ const String _kDefaultDebugLabel = 'unknown';
/// )
/// ```
///
/// ### Opacity
/// ### Opacity and Color
///
/// Each line here is progressively more opaque. The base color is
/// [material.Colors.black], and [Color.withOpacity] is used to create a
......@@ -47,6 +50,9 @@ const String _kDefaultDebugLabel = 'unknown';
/// [RichText] widget is explicitly given the ambient [DefaultTextStyle], since
/// [RichText] does not do that automatically. The inner [TextStyle] objects are
/// implicitly mixed with the parent [TextSpan]'s [TextSpan.style].
///
/// If [color] is specified, [foreground] must be null and vice versa. [color] is
/// treated as a shorthand for `new Paint()..color = color`.
///
/// ```dart
/// new RichText(
......@@ -230,6 +236,7 @@ class TextStyle extends Diagnosticable {
this.textBaseline,
this.height,
this.locale,
this.foreground,
this.background,
this.decoration,
this.decorationColor,
......@@ -238,7 +245,10 @@ class TextStyle extends Diagnosticable {
String fontFamily,
String package,
}) : fontFamily = package == null ? fontFamily : 'packages/$package/$fontFamily',
assert(inherit != null);
assert(inherit != null),
// TODO(dnfield): once https://github.com/dart-lang/sdk/issues/33408 is finished, this can be replaced with
// assert(color == null || foreground == null, _kColorForegroundWarning);
assert(identical(color, null) || identical(foreground, null), _kColorForegroundWarning);
/// Whether null values are replaced with their value in an ancestor text
......@@ -250,6 +260,13 @@ class TextStyle extends Diagnosticable {
final bool inherit;
/// The color to use when painting the text.
///
/// If [foreground] is specified, this value must be null. The [color] property
/// is shorthand for `new Paint()..color = color`.
///
/// In [merge], [apply], and [lerp], conflicts between [color] and [foreground]
/// specification are resolved in [foreground]'s favor - i.e. if [foreground] is
/// specified in one place, it will dominate [color] in another.
final Color color;
/// The name of the font to use when painting the text (e.g., Roboto). If the
......@@ -308,6 +325,21 @@ class TextStyle extends Diagnosticable {
/// region-specifc glyphs for each text span.
final Locale locale;
/// The paint drawn as a foreground for the text.
///
/// The value should ideally be cached and reused each time if multiple text
/// styles are created with the same paint settings. Otherwise, each time it
/// will appear like the style changed, which will result in unnecessary
/// updates all the way through the framework.
///
/// If [color] is specified, this value must be null. The [color] property
/// is shorthand for `new Paint()..color = color`.
///
/// In [merge], [apply], and [lerp], conflicts between [color] and [foreground]
/// specification are resolved in [foreground]'s favor - i.e. if [foreground] is
/// specified in one place, it will dominate [color] in another.
final Paint foreground;
/// The paint drawn as a background for the text.
///
/// The value should ideally be cached and reused each time if multiple text
......@@ -340,6 +372,9 @@ class TextStyle extends Diagnosticable {
/// Creates a copy of this text style but with the given fields replaced with
/// the new values.
///
/// One of [color] or [foreground] must be null, and if this has [foreground]
/// specified it will be given preference over any color parameter.
TextStyle copyWith({
Color color,
String fontFamily,
......@@ -351,12 +386,14 @@ class TextStyle extends Diagnosticable {
TextBaseline textBaseline,
double height,
Locale locale,
Paint foreground,
Paint background,
TextDecoration decoration,
Color decorationColor,
TextDecorationStyle decorationStyle,
String debugLabel,
}) {
assert(color == null || foreground == null, _kColorForegroundWarning);
String newDebugLabel;
assert(() {
if (this.debugLabel != null)
......@@ -365,7 +402,7 @@ class TextStyle extends Diagnosticable {
}());
return new TextStyle(
inherit: inherit,
color: color ?? this.color,
color: this.foreground == null && foreground == null ? color ?? this.color : null,
fontFamily: fontFamily ?? this.fontFamily,
fontSize: fontSize ?? this.fontSize,
fontWeight: fontWeight ?? this.fontWeight,
......@@ -375,6 +412,7 @@ class TextStyle extends Diagnosticable {
textBaseline: textBaseline ?? this.textBaseline,
height: height ?? this.height,
locale: locale ?? this.locale,
foreground: foreground ?? this.foreground,
background: background ?? this.background,
decoration: decoration ?? this.decoration,
decorationColor: decorationColor ?? this.decorationColor,
......@@ -388,6 +426,8 @@ class TextStyle extends Diagnosticable {
///
/// The non-numeric properties [color], [fontFamily], [decoration],
/// [decorationColor] and [decorationStyle] are replaced with the new values.
///
/// [foreground] will be given preference over [color] if it is not null.
///
/// The numeric properties are multiplied by the given factors and then
/// incremented by the given deltas.
......@@ -404,6 +444,9 @@ class TextStyle extends Diagnosticable {
///
/// If the underlying values are null, then the corresponding factors and/or
/// deltas must not be specified.
///
/// If [foreground] is specified on this object, then applying [color] here
/// will have no effect.
TextStyle apply({
Color color,
TextDecoration decoration,
......@@ -444,7 +487,7 @@ class TextStyle extends Diagnosticable {
return new TextStyle(
inherit: inherit,
color: color ?? this.color,
color: foreground == null ? color ?? this.color : null,
fontFamily: fontFamily ?? this.fontFamily,
fontSize: fontSize == null ? null : fontSize * fontSizeFactor + fontSizeDelta,
fontWeight: fontWeight == null ? null : FontWeight.values[(fontWeight.index + fontWeightDelta).clamp(0, FontWeight.values.length - 1)],
......@@ -454,6 +497,7 @@ class TextStyle extends Diagnosticable {
textBaseline: textBaseline,
height: height == null ? null : height * heightFactor + heightDelta,
locale: locale,
foreground: foreground != null ? foreground : null,
background: background,
decoration: decoration ?? this.decoration,
decorationColor: decorationColor ?? this.decorationColor,
......@@ -476,6 +520,9 @@ class TextStyle extends Diagnosticable {
/// inherit properties of this style.
///
/// If the given text style is null, returns this text style.
///
/// One of [color] or [foreground] must be null, and if this or `other` has
/// [foreground] specified it will be given preference over any color parameter.
TextStyle merge(TextStyle other) {
if (other == null)
return this;
......@@ -500,6 +547,7 @@ class TextStyle extends Diagnosticable {
textBaseline: other.textBaseline,
height: other.height,
locale: other.locale,
foreground: other.foreground,
background: other.background,
decoration: other.decoration,
decorationColor: other.decorationColor,
......@@ -523,6 +571,10 @@ class TextStyle extends Diagnosticable {
///
/// Values for `t` are usually obtained from an [Animation<double>], such as
/// an [AnimationController].
///
/// If [foreground] is specified on either of `a` or `b`, both will be treated
/// as if they have a [foreground] paint (creating a new [Paint] if necessary
/// based on the [color] property).
static TextStyle lerp(TextStyle a, TextStyle b, double t) {
assert(t != null);
assert(a == null || b == null || a.inherit == b.inherit);
......@@ -549,6 +601,7 @@ class TextStyle extends Diagnosticable {
textBaseline: t < 0.5 ? null : b.textBaseline,
height: t < 0.5 ? null : b.height,
locale: t < 0.5 ? null : b.locale,
foreground: t < 0.5 ? null : b.foreground,
background: t < 0.5 ? null : b.background,
decoration: t < 0.5 ? null : b.decoration,
decorationColor: Color.lerp(null, b.decorationColor, t),
......@@ -570,6 +623,7 @@ class TextStyle extends Diagnosticable {
textBaseline: t < 0.5 ? a.textBaseline : null,
height: t < 0.5 ? a.height : null,
locale: t < 0.5 ? a.locale : null,
foreground: t < 0.5 ? a.foreground : null,
background: t < 0.5 ? a.background : null,
decoration: t < 0.5 ? a.decoration : null,
decorationColor: Color.lerp(a.decorationColor, null, t),
......@@ -580,7 +634,7 @@ class TextStyle extends Diagnosticable {
return new TextStyle(
inherit: b.inherit,
color: Color.lerp(a.color, b.color, t),
color: a.foreground == null && b.foreground == null ? Color.lerp(a.color, b.color, t) : null,
fontFamily: t < 0.5 ? a.fontFamily : b.fontFamily,
fontSize: ui.lerpDouble(a.fontSize ?? b.fontSize, b.fontSize ?? a.fontSize, t),
fontWeight: FontWeight.lerp(a.fontWeight, b.fontWeight, t),
......@@ -590,6 +644,11 @@ class TextStyle extends Diagnosticable {
textBaseline: t < 0.5 ? a.textBaseline : b.textBaseline,
height: ui.lerpDouble(a.height ?? b.height, b.height ?? a.height, t),
locale: t < 0.5 ? a.locale : b.locale,
foreground: (a.foreground != null || b.foreground != null)
? t < 0.5
? a.foreground ?? (new Paint()..color = a.color)
: b.foreground ?? (new Paint()..color = b.color)
: null,
background: t < 0.5 ? a.background : b.background,
decoration: t < 0.5 ? a.decoration : b.decoration,
decorationColor: Color.lerp(a.decorationColor, b.decorationColor, t),
......@@ -614,6 +673,7 @@ class TextStyle extends Diagnosticable {
wordSpacing: wordSpacing,
height: height,
locale: locale,
foreground: foreground,
background: background,
);
}
......@@ -669,6 +729,7 @@ class TextStyle extends Diagnosticable {
textBaseline != other.textBaseline ||
height != other.height ||
locale != other.locale ||
foreground != other.foreground ||
background != other.background)
return RenderComparison.layout;
if (color != other.color ||
......@@ -697,6 +758,7 @@ class TextStyle extends Diagnosticable {
textBaseline == typedOther.textBaseline &&
height == typedOther.height &&
locale == typedOther.locale &&
foreground == typedOther.foreground &&
background == typedOther.background &&
decoration == typedOther.decoration &&
decorationColor == typedOther.decorationColor &&
......@@ -717,6 +779,7 @@ class TextStyle extends Diagnosticable {
textBaseline,
height,
locale,
foreground,
background,
decoration,
decorationColor,
......@@ -783,8 +846,9 @@ class TextStyle extends Diagnosticable {
styles.add(new DoubleProperty('${prefix}wordSpacing', wordSpacing, defaultValue: null));
styles.add(new EnumProperty<TextBaseline>('${prefix}baseline', textBaseline, defaultValue: null));
styles.add(new DoubleProperty('${prefix}height', height, unit: 'x', defaultValue: null));
styles.add(new StringProperty('${prefix}locale', locale?.toString(), defaultValue: null, quoted: false));
styles.add(new StringProperty('${prefix}background', background?.toString(), defaultValue: null, quoted: false));
styles.add(new DiagnosticsProperty<Locale>('${prefix}locale', locale, defaultValue: null));
styles.add(new DiagnosticsProperty<Paint>('${prefix}foreground', foreground, defaultValue: null));
styles.add(new DiagnosticsProperty<Paint>('${prefix}background', background, defaultValue: null));
if (decoration != null || decorationColor != null || decorationStyle != null) {
final List<String> decorationDescription = <String>[];
if (decorationStyle != null)
......
......@@ -452,6 +452,7 @@ class _TextStyleProxy implements TextStyle {
@override FontWeight get fontWeight => _delegate.fontWeight;
@override double get height => _delegate.height;
@override Locale get locale => _delegate.locale;
@override ui.Paint get foreground => _delegate.foreground;
@override ui.Paint get background => _delegate.background;
@override bool get inherit => _delegate.inherit;
@override double get letterSpacing => _delegate.letterSpacing;
......@@ -479,7 +480,7 @@ class _TextStyleProxy implements TextStyle {
}
@override
TextStyle copyWith({Color color, String fontFamily, double fontSize, FontWeight fontWeight, FontStyle fontStyle, double letterSpacing, double wordSpacing, TextBaseline textBaseline, double height, Locale locale, ui.Paint background, TextDecoration decoration, Color decorationColor, TextDecorationStyle decorationStyle, String debugLabel}) {
TextStyle copyWith({Color color, String fontFamily, double fontSize, FontWeight fontWeight, FontStyle fontStyle, double letterSpacing, double wordSpacing, TextBaseline textBaseline, double height, Locale locale, ui.Paint foreground, ui.Paint background, TextDecoration decoration, Color decorationColor, TextDecorationStyle decorationStyle, String debugLabel}) {
throw new UnimplementedError();
}
......
......@@ -216,4 +216,45 @@ void main() {
expect(TextStyle.lerp(foo, bar, 0.5).debugLabel, 'lerp(foo ⎯0.5→ bar)');
expect(TextStyle.lerp(foo.merge(bar), baz, 0.51).copyWith().debugLabel, '(lerp((foo).merge(bar) ⎯0.5→ baz)).copyWith');
});
test('TextStyle foreground and color combos', () {
const Color red = const Color.fromARGB(255, 255, 0, 0);
const Color blue = const Color.fromARGB(255, 0, 0, 255);
const TextStyle redTextStyle = const TextStyle(color: red);
const TextStyle blueTextStyle = const TextStyle(color: blue);
final TextStyle redPaintTextStyle = new TextStyle(foreground: new Paint()..color = red);
final TextStyle bluePaintTextStyle = new TextStyle(foreground: new Paint()..color = blue);
// merge/copyWith
final TextStyle redBlueBothForegroundMerged = redTextStyle.merge(blueTextStyle);
expect(redBlueBothForegroundMerged.color, blue);
expect(redBlueBothForegroundMerged.foreground, isNull);
final TextStyle redBlueBothPaintMerged = redPaintTextStyle.merge(bluePaintTextStyle);
expect(redBlueBothPaintMerged.color, null);
expect(redBlueBothPaintMerged.foreground, bluePaintTextStyle.foreground);
final TextStyle redPaintBlueColorMerged = redPaintTextStyle.merge(blueTextStyle);
expect(redPaintBlueColorMerged.color, null);
expect(redPaintBlueColorMerged.foreground, redPaintTextStyle.foreground);
final TextStyle blueColorRedPaintMerged = blueTextStyle.merge(redPaintTextStyle);
expect(blueColorRedPaintMerged.color, null);
expect(blueColorRedPaintMerged.foreground, redPaintTextStyle.foreground);
// apply
expect(redPaintTextStyle.apply(color: blue).color, isNull);
expect(redPaintTextStyle.apply(color: blue).foreground.color, red);
expect(redTextStyle.apply(color: blue).color, blue);
// lerp
expect(TextStyle.lerp(redTextStyle, blueTextStyle, .25).color, Color.lerp(red, blue, .25));
expect(TextStyle.lerp(redTextStyle, bluePaintTextStyle, .25).color, isNull);
expect(TextStyle.lerp(redTextStyle, bluePaintTextStyle, .25).foreground.color, red);
expect(TextStyle.lerp(redTextStyle, bluePaintTextStyle, .75).foreground.color, blue);
expect(TextStyle.lerp(redPaintTextStyle, bluePaintTextStyle, .25).color, isNull);
expect(TextStyle.lerp(redPaintTextStyle, bluePaintTextStyle, .25).foreground.color, red);
expect(TextStyle.lerp(redPaintTextStyle, bluePaintTextStyle, .75).foreground.color, blue);
});
}
......@@ -59,4 +59,84 @@ void main() {
skip: !Platform.isLinux,
);
});
testWidgets('Text Foreground', (WidgetTester tester) async {
const Color black = const Color(0xFF000000);
const Color red = const Color(0xFFFF0000);
const Color blue = const Color(0xFF0000FF);
final Shader linearGradient = const LinearGradient(colors: <Color>[red, blue]).createShader(new Rect.fromLTWH(0.0, 0.0, 50.0, 20.0));
await tester.pumpWidget(
new RepaintBoundary(
child: new Center(
child: new Text('Hello',
textDirection: TextDirection.ltr,
style: new TextStyle(
foreground: new Paint()
..color = black
..shader = linearGradient
),
),
),
),
);
await expectLater(
find.byType(RepaintBoundary),
matchesGoldenFile('text_golden.Foreground.gradient.png'),
// this was generated on MacOS and will fail on Linux
// change to !Platform.isLinux once they're properly generated
skip: true, // !Platform.isLinux,
);
await tester.pumpWidget(
new RepaintBoundary(
child: new Center(
child: new Text('Hello',
textDirection: TextDirection.ltr,
style: new TextStyle(
foreground: new Paint()
..color = black
..style = PaintingStyle.stroke
..strokeWidth = 2.0
),
),
),
),
);
await expectLater(
find.byType(RepaintBoundary),
matchesGoldenFile('text_golden.Foreground.stroke.png'),
// this was generated on MacOS and will fail on Linux
// change to !Platform.isLinux once they're properly generated
skip: true, // !Platform.isLinux,
);
await tester.pumpWidget(
new RepaintBoundary(
child: new Center(
child: new Text('Hello',
textDirection: TextDirection.ltr,
style: new TextStyle(
foreground: new Paint()
..color = black
..style = PaintingStyle.stroke
..strokeWidth = 2.0
..shader = linearGradient
),
),
),
),
);
await expectLater(
find.byType(RepaintBoundary),
matchesGoldenFile('text_golden.Foreground.stroke_and_gradient.png'),
// this was generated on MacOS and will fail on Linux
// change to !Platform.isLinux once they're properly generated
skip: true, // !Platform.isLinux,
);
});
}
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