text_style.dart 22.7 KB
Newer Older
1 2 3 4
// Copyright 2015 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.

5
import 'dart:ui' as ui show ParagraphStyle, TextStyle, lerpDouble;
6

7 8
import 'package:flutter/foundation.dart';

9
import 'basic_types.dart';
10

Florian Loitsch's avatar
Florian Loitsch committed
11
/// An immutable style in which paint text.
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
///
/// ## Sample code
///
/// ### Bold
///
/// Here, a single line of text in a [Text] widget is given a specific style
/// override. The style is mixed with the ambient [DefaultTextStyle] by the
/// [Text] widget.
///
/// ```dart
/// new Text(
///   'No, we need bold strokes. We need this plan.',
///   style: new TextStyle(fontWeight: FontWeight.bold),
/// )
/// ```
///
/// ### Italics
///
/// As in the previous example, the [Text] widget is given a specific style
/// override which is implicitly mixed with the ambient [DefaultTextStyle].
///
/// ```dart
/// new Text(
///   'Welcome to the present, we\'re running a real nation.',
///   style: new TextStyle(fontStyle: FontStyle.italic),
/// )
/// ```
///
/// ### Opacity
///
/// Each line here is progressively more opaque. The base color is
43 44 45 46 47
/// [material.Colors.black], and [Color.withOpacity] is used to create a
/// derivative color with the desired opacity. The root [TextSpan] for this
/// [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].
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
///
/// ```dart
/// new RichText(
///   text: new TextSpan(
///     style: DefaultTextStyle.of(context).style,
///     children: <TextSpan>[
///       new TextSpan(
///         text: 'You don\'t have the votes.\n',
///         style: new TextStyle(color: Colors.black.withOpacity(0.6)),
///       ),
///       new TextSpan(
///         text: 'You don\'t have the votes!\n',
///         style: new TextStyle(color: Colors.black.withOpacity(0.8)),
///       ),
///       new TextSpan(
///         text: 'You\'re gonna need congressional approval and you don\'t have the votes!\n',
///         style: new TextStyle(color: Colors.black.withOpacity(1.0)),
///       ),
///     ],
///   ),
/// )
/// ```
///
/// ### Size
///
/// In this example, the ambient [DefaultTextStyle] is explicitly manipulated to
/// obtain a [TextStyle] that doubles the default font size.
///
/// ```dart
/// new Text(
///   'These are wise words, enterprising men quote \'em.',
///   style: DefaultTextStyle.of(context).style.apply(fontSizeFactor: 2.0),
/// )
/// ```
///
/// ### Line height
///
/// The [height] property can be used to change the line height. Here, the line
/// height is set to 100 logical pixels, so that the text is very spaced out.
///
/// ```dart
/// new Text(
///   'Don\'t act surprised, you guys, cuz I wrote \'em!',
///   style: new TextStyle(height: 100.0),
/// )
/// ```
///
/// ### Wavy red underline with black text
///
/// Styles can be combined. In this example, the misspelt word is drawn in black
/// text and underlined with a wavy red line to indicate a spelling error. (The
/// remainder is styled according to the Flutter default text styles, not the
/// ambient [DefaultTextStyle], since no explicit style is given and [RichText]
/// does not automatically use the ambient [DefaultTextStyle].)
///
/// ```dart
/// new RichText(
///   text: new TextSpan(
///     text: 'Don\'t tax the South ',
///     children: <TextSpan>[
///       new TextSpan(
///         text: 'cuz',
///         style: new TextStyle(
///           color: Colors.black,
///           decoration: TextDecoration.underline,
///           decorationColor: Colors.red,
///           decorationStyle: TextDecorationStyle.wavy,
///         ),
///       ),
///       new TextSpan(
///         text: ' we got it made in the shade',
///       ),
///     ],
///   ),
/// )
/// ```
///
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
/// ### Custom Fonts
///
/// Custom fonts can be declared in the `pubspec.yaml` file as shown below:
///
///```yaml
/// flutter:
///   fonts:
///     - family: Raleway
///       fonts:
///         - asset: fonts/Raleway-Regular.ttf
///         - asset: fonts/Raleway-Medium.ttf
///           weight: 500
///         - asset: assets/fonts/Raleway-SemiBold.ttf
///           weight: 600
///      - family: Schyler
///        fonts:
///          - asset: fonts/Schyler-Regular.ttf
///          - asset: fonts/Schyler-Italic.ttf
///            style: italic
///```
///
/// The `family` property determines the name of the font, which you can use in
/// the [fontFamily] argument. The `asset` property is a path to the font file,
/// relative to the [pubspec.yaml] file. The `weight` property specifies the
/// weight of the glyph outlines in the file as an integer multiple of 100
/// between 100 and 900. This corresponds to the [FontWeight] class and can be
/// used in the [fontWeight] argument. The `style` property specfies whether the
/// outlines in the file are `italic` or `normal`. These values correspond to
/// the [FontStyle] class and can be used in the [fontStyle] argument.
///
/// To select a custom font, create [TextStyle] using the [fontFamily]
/// argument as shown in the example below:
///
/// ```dart
/// const TextStyle(fontFamily: 'Raleway')
/// ```
///
/// To use a font family defined in a package, the [package] argument must be
/// provided. For instance, suppose the font declaration above is in the
/// `pubspec.yaml` of a package named `my_package` which the app depends on.
/// Then creating the TextStyle is done as follows:
///
/// ```dart
/// const TextStyle(fontFamily: 'Raleway', package: 'my_package')
/// ```
///
/// This is also how the package itself should create the style.
///
/// A package can also provide font files in its `lib/` folder which will not
/// automatically be included in the app. Instead the app can use these
/// selectively when declaring a font. Suppose a package named `my_package` has:
///
/// ```
/// lib/fonts/Raleway-Medium.ttf
/// ```
///
/// Then the app can declare a font like in the example below:
///
///```yaml
/// flutter:
///   fonts:
///     - family: Raleway
///       fonts:
///         - asset: assets/fonts/Raleway-Regular.ttf
///         - asset: packages/my_package/fonts/Raleway-Medium.ttf
///           weight: 500
///```
///
/// The `lib/` is implied, so it should not be included in the asset path.
///
195 196 197 198 199 200 201 202
/// See also:
///
///  * [Text], the widget for showing text in a single style.
///  * [DefaultTextStyle], the widget that specifies the default text styles for
///    [Text] widgets, configured using a [TextStyle].
///  * [RichText], the widget for showing a paragraph of mix-style text.
///  * [TextSpan], the class that wraps a [TextStyle] for the purposes of
///    passing it to a [RichText].
203
@immutable
204
class TextStyle extends Diagnosticable {
205
  /// Creates a text style.
206
  const TextStyle({
207
    this.inherit: true,
208
    this.color,
209
    String fontFamily,
210 211
    this.fontSize,
    this.fontWeight,
212
    this.fontStyle,
213
    this.letterSpacing,
214
    this.wordSpacing,
215
    this.textBaseline,
216 217 218
    this.height,
    this.decoration,
    this.decorationColor,
219
    this.decorationStyle,
220 221 222 223
    this.package,
  }) : fontFamily = package == null ? fontFamily : 'packages/$package/$fontFamily',
       assert(inherit != null);

224

225 226 227 228 229 230
  /// Whether null values are replaced with their value in an ancestor text
  /// style (e.g., in a [TextSpan] tree).
  ///
  /// If this is false, properties that don't have explicit values will revert
  /// to the defaults: white in color, a font size of 10 pixels, in a sans-serif
  /// font face.
231 232
  final bool inherit;

233
  /// The color to use when painting the text.
234
  final Color color;
235

236
  /// The name of the font to use when painting the text (e.g., Roboto).
237
  final String fontFamily;
238

239 240 241 242
  /// The name of the package from which the font is included. See the
  /// documentation for the [TextStyle] class itself for details.
  final String package;

243
  /// The size of glyphs (in logical pixels) to use when painting the text.
244 245 246 247
  ///
  /// During painting, the [fontSize] is multiplied by the current
  /// `textScaleFactor` to let users make it easier to read text by increasing
  /// its size.
248 249
  final double fontSize;

250
  /// The typeface thickness to use when painting the text (e.g., bold).
251
  final FontWeight fontWeight;
252

253
  /// The typeface variant to use when drawing the letters (e.g., italics).
254
  final FontStyle fontStyle;
255

256
  /// The amount of space (in logical pixels) to add between each letter.
xster's avatar
xster committed
257
  /// A negative value can be used to bring the letters closer.
258 259
  final double letterSpacing;

260 261 262
  /// The amount of space (in logical pixels) to add at each sequence of
  /// white-space (i.e. between each word). A negative value can be used to
  /// bring the words closer.
263 264
  final double wordSpacing;

265 266
  /// The common baseline that should be aligned between this text span and its
  /// parent text span, or, for the root text spans, with the line box.
267
  final TextBaseline textBaseline;
268

269 270 271
  /// The height of this text span, as a multiple of the font size.
  ///
  /// If applied to the root [TextSpan], this value sets the line height, which
272 273
  /// is the minimum distance between subsequent text baselines, as multiple of
  /// the font size.
274 275
  final double height;

276
  /// The decorations to paint near the text (e.g., an underline).
277
  final TextDecoration decoration;
278

279
  /// The color in which to paint the text decorations.
280
  final Color decorationColor;
281

282
  /// The style in which to paint the text decorations (e.g., dashed).
283 284
  final TextDecorationStyle decorationStyle;

285 286
  /// Creates a copy of this text style but with the given fields replaced with
  /// the new values.
287 288 289 290 291
  TextStyle copyWith({
    Color color,
    String fontFamily,
    double fontSize,
    FontWeight fontWeight,
292
    FontStyle fontStyle,
293
    double letterSpacing,
294
    double wordSpacing,
295
    TextBaseline textBaseline,
296
    double height,
297
    TextDecoration decoration,
298
    Color decorationColor,
299
    TextDecorationStyle decorationStyle,
300 301
  }) {
    return new TextStyle(
302
      inherit: inherit,
Ian Hickson's avatar
Ian Hickson committed
303 304 305 306 307 308 309 310 311 312 313
      color: color ?? this.color,
      fontFamily: fontFamily ?? this.fontFamily,
      fontSize: fontSize ?? this.fontSize,
      fontWeight: fontWeight ?? this.fontWeight,
      fontStyle: fontStyle ?? this.fontStyle,
      letterSpacing: letterSpacing ?? this.letterSpacing,
      wordSpacing: wordSpacing ?? this.wordSpacing,
      textBaseline: textBaseline ?? this.textBaseline,
      height: height ?? this.height,
      decoration: decoration ?? this.decoration,
      decorationColor: decorationColor ?? this.decorationColor,
314
      decorationStyle: decorationStyle ?? this.decorationStyle,
315 316 317
    );
  }

Ian Hickson's avatar
Ian Hickson committed
318 319 320
  /// Creates a copy of this text style but with the numeric fields multiplied
  /// by the given factors and then incremented by the given deltas.
  ///
321 322
  /// For example, `style.apply(fontSizeFactor: 2.0, fontSizeDelta: 1.0)` would
  /// return a [TextStyle] whose [fontSize] is `style.fontSize * 2.0 + 1.0`.
Ian Hickson's avatar
Ian Hickson committed
323 324 325 326 327 328 329 330 331 332 333
  ///
  /// For the [fontWeight], the delta is applied to the [FontWeight] enum index
  /// values, so that for instance `style.apply(fontWeightDelta: -2)` when
  /// applied to a `style` whose [fontWeight] is [FontWeight.w500] will return a
  /// [TextStyle] with a [FontWeight.w300].
  ///
  /// The arguments must not be null.
  ///
  /// If the underlying values are null, then the corresponding factors and/or
  /// deltas must not be specified.
  ///
334
  /// The non-numeric fields can be controlled using the corresponding arguments.
Ian Hickson's avatar
Ian Hickson committed
335
  TextStyle apply({
336 337
    Color color,
    String fontFamily,
Ian Hickson's avatar
Ian Hickson committed
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
    double fontSizeFactor: 1.0,
    double fontSizeDelta: 0.0,
    int fontWeightDelta: 0,
    double letterSpacingFactor: 1.0,
    double letterSpacingDelta: 0.0,
    double wordSpacingFactor: 1.0,
    double wordSpacingDelta: 0.0,
    double heightFactor: 1.0,
    double heightDelta: 0.0,
  }) {
    assert(fontSizeFactor != null);
    assert(fontSizeDelta != null);
    assert(fontSize != null || (fontSizeFactor == 1.0 && fontSizeDelta == 0.0));
    assert(fontWeightDelta != null);
    assert(fontWeight != null || fontWeightDelta == 0.0);
    assert(letterSpacingFactor != null);
    assert(letterSpacingDelta != null);
    assert(letterSpacing != null || (letterSpacingFactor == 1.0 && letterSpacingDelta == 0.0));
    assert(wordSpacingFactor != null);
    assert(wordSpacingDelta != null);
    assert(wordSpacing != null || (wordSpacingFactor == 1.0 && wordSpacingDelta == 0.0));
    assert(heightFactor != null);
    assert(heightDelta != null);
    assert(heightFactor != null || (heightFactor == 1.0 && heightDelta == 0.0));
    return new TextStyle(
      inherit: inherit,
364 365
      color: color ?? this.color,
      fontFamily: fontFamily ?? this.fontFamily,
Ian Hickson's avatar
Ian Hickson committed
366 367 368 369 370 371 372 373 374 375 376 377 378
      fontSize: fontSize == null ? null : fontSize * fontSizeFactor + fontSizeDelta,
      fontWeight: fontWeight == null ? null : FontWeight.values[(fontWeight.index + fontWeightDelta).clamp(0, FontWeight.values.length - 1)],
      fontStyle: fontStyle,
      letterSpacing: letterSpacing == null ? null : letterSpacing * letterSpacingFactor + letterSpacingDelta,
      wordSpacing: wordSpacing == null ? null : wordSpacing * wordSpacingFactor + wordSpacingDelta,
      textBaseline: textBaseline,
      height: height == null ? null : height * heightFactor + heightDelta,
      decoration: decoration,
      decorationColor: decorationColor,
      decorationStyle: decorationStyle,
    );
  }

379
  /// Returns a new text style that matches this text style but with some values
380 381
  /// replaced by the non-null parameters of the given text style. If the given
  /// text style is null, simply returns this text style.
382
  TextStyle merge(TextStyle other) {
383 384 385
    if (other == null)
      return this;
    assert(other.inherit);
386 387 388 389 390
    return copyWith(
      color: other.color,
      fontFamily: other.fontFamily,
      fontSize: other.fontSize,
      fontWeight: other.fontWeight,
391
      fontStyle: other.fontStyle,
392
      letterSpacing: other.letterSpacing,
393
      wordSpacing: other.wordSpacing,
394
      textBaseline: other.textBaseline,
395 396 397 398 399 400 401
      height: other.height,
      decoration: other.decoration,
      decorationColor: other.decorationColor,
      decorationStyle: other.decorationStyle
    );
  }

402 403 404 405 406 407 408 409 410 411
  /// Interpolate between two text styles.
  ///
  /// This will not work well if the styles don't set the same fields.
  static TextStyle lerp(TextStyle begin, TextStyle end, double t) {
    assert(begin.inherit == end.inherit);
    return new TextStyle(
      inherit: end.inherit,
      color: Color.lerp(begin.color, end.color, t),
      fontFamily: t < 0.5 ? begin.fontFamily : end.fontFamily,
      fontSize: ui.lerpDouble(begin.fontSize ?? end.fontSize, end.fontSize ?? begin.fontSize, t),
Ian Hickson's avatar
Ian Hickson committed
412
      fontWeight: FontWeight.lerp(begin.fontWeight, end.fontWeight, t),
413 414 415 416 417 418 419 420 421 422
      fontStyle: t < 0.5 ? begin.fontStyle : end.fontStyle,
      letterSpacing: ui.lerpDouble(begin.letterSpacing ?? end.letterSpacing, end.letterSpacing ?? begin.letterSpacing, t),
      wordSpacing: ui.lerpDouble(begin.wordSpacing ?? end.wordSpacing, end.wordSpacing ?? begin.wordSpacing, t),
      textBaseline: t < 0.5 ? begin.textBaseline : end.textBaseline,
      height: ui.lerpDouble(begin.height ?? end.height, end.height ?? begin.height, t),
      decoration: t < 0.5 ? begin.decoration : end.decoration,
      decorationColor: Color.lerp(begin.decorationColor, end.decorationColor, t),
      decorationStyle: t < 0.5 ? begin.decorationStyle : end.decorationStyle
    );
  }
423

424
  /// The style information for text runs, encoded for use by `dart:ui`.
425
  ui.TextStyle getTextStyle({ double textScaleFactor: 1.0 }) {
426 427 428 429 430 431 432
    return new ui.TextStyle(
      color: color,
      decoration: decoration,
      decorationColor: decorationColor,
      decorationStyle: decorationStyle,
      fontWeight: fontWeight,
      fontStyle: fontStyle,
433
      textBaseline: textBaseline,
434
      fontFamily: fontFamily,
435
      fontSize: fontSize == null ? null : fontSize * textScaleFactor,
436 437
      letterSpacing: letterSpacing,
      wordSpacing: wordSpacing,
Adam Barth's avatar
Adam Barth committed
438
      height: height
439 440 441
    );
  }

442
  /// The style information for paragraphs, encoded for use by `dart:ui`.
443 444 445 446
  ///
  /// The `textScaleFactor` argument must not be null. If omitted, it defaults
  /// to 1.0. The other arguments may be null. The `maxLines` argument, if
  /// specified and non-null, must be greater than zero.
447 448
  ui.ParagraphStyle getParagraphStyle({
      TextAlign textAlign,
Ian Hickson's avatar
Ian Hickson committed
449
      TextDirection textDirection,
450 451
      double textScaleFactor: 1.0,
      String ellipsis,
452
      int maxLines,
453 454 455
  }) {
    assert(textScaleFactor != null);
    assert(maxLines == null || maxLines > 0);
456
    return new ui.ParagraphStyle(
457
      textAlign: textAlign,
Ian Hickson's avatar
Ian Hickson committed
458
      textDirection: textDirection,
Adam Barth's avatar
Adam Barth committed
459 460 461
      fontWeight: fontWeight,
      fontStyle: fontStyle,
      fontFamily: fontFamily,
462
      fontSize: fontSize == null ? null : fontSize * textScaleFactor,
463
      lineHeight: height,
464
      maxLines: maxLines,
465
      ellipsis: ellipsis,
466 467
    );
  }
468

469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
  /// Describe the difference between this style and another, in terms of how
  /// much damage it will make to the rendering.
  ///
  /// See also:
  ///
  ///  * [TextSpan.compareTo], which does the same thing for entire [TextSpan]s.
  RenderComparison compareTo(TextStyle other) {
    if (identical(this, other))
      return RenderComparison.identical;
    if (inherit != other.inherit ||
        fontFamily != other.fontFamily ||
        fontSize != other.fontSize ||
        fontWeight != other.fontWeight ||
        fontStyle != other.fontStyle ||
        letterSpacing != other.letterSpacing ||
        wordSpacing != other.wordSpacing ||
        textBaseline != other.textBaseline ||
        height != other.height)
      return RenderComparison.layout;
    if (color != other.color ||
        decoration != other.decoration ||
        decorationColor != other.decorationColor ||
        decorationStyle != other.decorationStyle)
      return RenderComparison.paint;
    return RenderComparison.identical;
  }

496
  @override
Hixie's avatar
Hixie committed
497
  bool operator ==(dynamic other) {
498 499
    if (identical(this, other))
      return true;
Ian Hickson's avatar
Ian Hickson committed
500
    if (other.runtimeType != runtimeType)
Hixie's avatar
Hixie committed
501 502
      return false;
    final TextStyle typedOther = other;
503 504
    return inherit == typedOther.inherit &&
           color == typedOther.color &&
Hixie's avatar
Hixie committed
505 506 507 508
           fontFamily == typedOther.fontFamily &&
           fontSize == typedOther.fontSize &&
           fontWeight == typedOther.fontWeight &&
           fontStyle == typedOther.fontStyle &&
509
           letterSpacing == typedOther.letterSpacing &&
510
           wordSpacing == typedOther.wordSpacing &&
Hixie's avatar
Hixie committed
511
           textBaseline == typedOther.textBaseline &&
512
           height == typedOther.height &&
Hixie's avatar
Hixie committed
513 514 515
           decoration == typedOther.decoration &&
           decorationColor == typedOther.decorationColor &&
           decorationStyle == typedOther.decorationStyle;
516 517
  }

518
  @override
519
  int get hashCode {
520
    return hashValues(
521
      inherit,
522 523 524 525 526 527
      color,
      fontFamily,
      fontSize,
      fontWeight,
      fontStyle,
      letterSpacing,
528
      wordSpacing,
529
      textBaseline,
530
      height,
531 532 533 534
      decoration,
      decorationColor,
      decorationStyle
    );
535 536
  }

537
  @override
538
  String toStringShort() => '$runtimeType';
539 540

  /// Adds all properties prefixing property names with the optional `prefix`.
541 542 543
  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties, { String prefix: '' }) {
    super.debugFillProperties(properties);
544 545 546 547 548
    final List<DiagnosticsNode> styles = <DiagnosticsNode>[];
    styles.add(new DiagnosticsProperty<Color>('${prefix}color', color, defaultValue: null));
    styles.add(new StringProperty('${prefix}family', fontFamily, defaultValue: null, quoted: false));
    styles.add(new DoubleProperty('${prefix}size', fontSize, defaultValue: null));
    String weightDescription;
Hixie's avatar
Hixie committed
549 550 551
    if (fontWeight != null) {
      switch (fontWeight) {
        case FontWeight.w100:
552
          weightDescription = '100';
Hixie's avatar
Hixie committed
553 554
          break;
        case FontWeight.w200:
555
          weightDescription = '200';
Hixie's avatar
Hixie committed
556 557
          break;
        case FontWeight.w300:
558
          weightDescription = '300';
Hixie's avatar
Hixie committed
559 560
          break;
        case FontWeight.w400:
561
          weightDescription = '400';
Hixie's avatar
Hixie committed
562 563
          break;
        case FontWeight.w500:
564
          weightDescription = '500';
Hixie's avatar
Hixie committed
565 566
          break;
        case FontWeight.w600:
567
          weightDescription = '600';
Hixie's avatar
Hixie committed
568 569
          break;
        case FontWeight.w700:
570
          weightDescription = '700';
Hixie's avatar
Hixie committed
571 572
          break;
        case FontWeight.w800:
573
          weightDescription = '800';
Hixie's avatar
Hixie committed
574 575
          break;
        case FontWeight.w900:
576
          weightDescription = '900';
Hixie's avatar
Hixie committed
577 578 579
          break;
      }
    }
580 581 582 583 584 585 586 587 588 589 590 591 592 593
    // TODO(jacobr): switch this to use enumProperty which will either cause the
    // weight description to change to w600 from 600 or require existing
    // enumProperty to handle this special case.
    styles.add(new DiagnosticsProperty<FontWeight>(
      '${prefix}weight',
      fontWeight,
      description: weightDescription,
      defaultValue: null,
    ));
    styles.add(new EnumProperty<FontStyle>('${prefix}style', fontStyle, defaultValue: null));
    styles.add(new DoubleProperty('${prefix}letterSpacing', letterSpacing, unit: 'x', defaultValue: null));
    styles.add(new DoubleProperty('${prefix}wordSpacing', wordSpacing, unit: 'x', defaultValue: null));
    styles.add(new EnumProperty<TextBaseline>('${prefix}baseline', textBaseline, defaultValue: null));
    styles.add(new DoubleProperty('${prefix}height', height, unit: 'x', defaultValue: null));
Hixie's avatar
Hixie committed
594
    if (decoration != null || decorationColor != null || decorationStyle != null) {
595 596 597 598 599 600
      final List<String> decorationDescription = <String>[];
      if (decorationStyle != null)
        decorationDescription.add(describeEnum((decorationStyle)));

      // Hide decorationColor from the default text view as it is shown in the
      // terse decoration summary as well.
601
      styles.add(new DiagnosticsProperty<Color>('${prefix}decorationColor', decorationColor, defaultValue: null, level: DiagnosticLevel.fine));
602 603 604 605 606 607 608

      if (decorationColor != null)
        decorationDescription.add('$decorationColor');

      // Intentionally collide with the property 'decoration' added below.
      // Tools that show hidden properties could choose the first property
      // matching the name to disambiguate.
609
      styles.add(new DiagnosticsProperty<TextDecoration>('${prefix}decoration', decoration, defaultValue: null, level: DiagnosticLevel.hidden));
610 611 612 613
      if (decoration != null)
        decorationDescription.add('$decoration');
      assert(decorationDescription.isNotEmpty);
      styles.add(new MessageProperty('${prefix}decoration', decorationDescription.join(' ')));
Hixie's avatar
Hixie committed
614
    }
615

616 617
    final bool styleSpecified = styles.any((DiagnosticsNode n) => !n.isFiltered(DiagnosticLevel.info));
    properties.add(new DiagnosticsProperty<bool>('${prefix}inherit', inherit, level: (!styleSpecified && inherit) ? DiagnosticLevel.fine : DiagnosticLevel.info));
618 619 620
    for (DiagnosticsNode style in styles)
      properties.add(style);

621 622
    if (!styleSpecified)
      properties.add(new FlagProperty('inherit', value: inherit, ifTrue: '$prefix<all styles inherited>', ifFalse: '$prefix<no style specified>'));
623 624
  }
}