| // 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/foundation.dart'; |
| |
| import 'basic.dart'; |
| import 'framework.dart'; |
| import 'media_query.dart'; |
| |
| /// The text style to apply to descendant [Text] widgets without explicit style. |
| class DefaultTextStyle extends InheritedWidget { |
| /// Creates a default text style for the given subtree. |
| /// |
| /// Consider using [DefaultTextStyle.merge] to inherit styling information |
| /// from the current default text style for a given [BuildContext]. |
| /// |
| /// The [style] and [child] arguments are required and must not be null. |
| /// |
| /// The [softWrap] and [overflow] arguments must not be null (though they do |
| /// have default values). |
| /// |
| /// The [maxLines] property may be null (and indeed defaults to null), but if |
| /// it is not null, it must be greater than zero. |
| const DefaultTextStyle({ |
| Key key, |
| @required this.style, |
| this.textAlign, |
| this.softWrap: true, |
| this.overflow: TextOverflow.clip, |
| this.maxLines, |
| @required Widget child, |
| }) : assert(style != null), |
| assert(softWrap != null), |
| assert(overflow != null), |
| assert(maxLines == null || maxLines > 0), |
| assert(child != null), |
| super(key: key, child: child); |
| |
| /// A const-constructible default text style that provides fallback values. |
| /// |
| /// Returned from [of] when the given [BuildContext] doesn't have an enclosing default text style. |
| /// |
| /// This constructor creates a [DefaultTextStyle] that lacks a [child], which |
| /// means the constructed value cannot be incorporated into the tree. |
| const DefaultTextStyle.fallback() |
| : style = const TextStyle(), |
| textAlign = null, |
| softWrap = true, |
| maxLines = null, |
| overflow = TextOverflow.clip; |
| |
| /// Creates a default text style that overrides the text styles in scope at |
| /// this point in the widget tree. |
| /// |
| /// The given [style] is merged with the [style] from the default text style |
| /// for the [BuildContext] where the widget is inserted, and any of the other |
| /// arguments that are not null replace the corresponding properties on that |
| /// same default text style. |
| /// |
| /// This constructor cannot be used to override the [maxLines] property of the |
| /// ancestor with the value null, since null here is used to mean "defer to |
| /// ancestor". To replace a non-null [maxLines] from an ancestor with the null |
| /// value (to remove the restriction on number of lines), manually obtain the |
| /// ambient [DefaultTextStyle] using [DefaultTextStyle.of], then create a new |
| /// [DefaultTextStyle] using the [new DefaultTextStyle] constructor directly. |
| /// See the source below for an example of how to do this (since that's |
| /// essentially what this constructor does). |
| static Widget merge({ |
| Key key, |
| TextStyle style, |
| TextAlign textAlign, |
| bool softWrap, |
| TextOverflow overflow, |
| int maxLines, |
| @required Widget child, |
| }) { |
| assert(child != null); |
| return new Builder( |
| builder: (BuildContext context) { |
| final DefaultTextStyle parent = DefaultTextStyle.of(context); |
| return new DefaultTextStyle( |
| key: key, |
| style: parent.style.merge(style), |
| textAlign: textAlign ?? parent.textAlign, |
| softWrap: softWrap ?? parent.softWrap, |
| overflow: overflow ?? parent.overflow, |
| maxLines: maxLines ?? parent.maxLines, |
| child: child, |
| ); |
| }, |
| ); |
| } |
| |
| /// The text style to apply. |
| final TextStyle style; |
| |
| /// How the text should be aligned horizontally. |
| final TextAlign textAlign; |
| |
| /// Whether the text should break at soft line breaks. |
| /// |
| /// If false, the glyphs in the text will be positioned as if there was unlimited horizontal space. |
| final bool softWrap; |
| |
| /// How visual overflow should be handled. |
| final TextOverflow overflow; |
| |
| /// An optional maximum number of lines for the text to span, wrapping if necessary. |
| /// If the text exceeds the given number of lines, it will be truncated according |
| /// to [overflow]. |
| /// |
| /// If this is 1, text will not wrap. Otherwise, text will be wrapped at the |
| /// edge of the box. |
| /// |
| /// If this is non-null, it will override even explicit null values of |
| /// [Text.maxLines]. |
| final int maxLines; |
| |
| /// The closest instance of this class that encloses the given context. |
| /// |
| /// If no such instance exists, returns an instance created by |
| /// [DefaultTextStyle.fallback], which contains fallback values. |
| /// |
| /// Typical usage is as follows: |
| /// |
| /// ```dart |
| /// DefaultTextStyle style = DefaultTextStyle.of(context); |
| /// ``` |
| static DefaultTextStyle of(BuildContext context) { |
| return context.inheritFromWidgetOfExactType(DefaultTextStyle) ?? const DefaultTextStyle.fallback(); |
| } |
| |
| @override |
| bool updateShouldNotify(DefaultTextStyle oldWidget) { |
| return style != oldWidget.style || |
| textAlign != oldWidget.textAlign || |
| softWrap != oldWidget.softWrap || |
| overflow != oldWidget.overflow || |
| maxLines != oldWidget.maxLines; |
| } |
| |
| @override |
| void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
| super.debugFillProperties(properties); |
| style?.debugFillProperties(properties); |
| properties.add(new EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: null)); |
| properties.add(new FlagProperty('softWrap', value: softWrap, ifTrue: 'wrapping at box width', ifFalse: 'no wrapping except at line break characters', showName: true)); |
| properties.add(new EnumProperty<TextOverflow>('overflow', overflow, defaultValue: null)); |
| properties.add(new IntProperty('maxLines', maxLines, defaultValue: null)); |
| } |
| } |
| |
| /// A run of text with a single style. |
| /// |
| /// The [Text] widget displays a string of text with single style. The string |
| /// might break across multiple lines or might all be displayed on the same line |
| /// depending on the layout constraints. |
| /// |
| /// The [style] argument is optional. When omitted, the text will use the style |
| /// from the closest enclosing [DefaultTextStyle]. If the given style's |
| /// [TextStyle.inherit] property is true, the given style will be merged with |
| /// the closest enclosing [DefaultTextStyle]. This merging behavior is useful, |
| /// for example, to make the text bold while using the default font family and |
| /// size. |
| /// |
| /// Using the [new TextSpan.rich] constructor, the [Text] widget can also be |
| /// created with a [TextSpan] to display text that use multiple styles |
| /// (e.g., a paragraph with some bold words). |
| /// |
| /// ## Sample code |
| /// |
| /// ```dart |
| /// new Text( |
| /// 'Hello, $_name! How are you?', |
| /// textAlign: TextAlign.center, |
| /// overflow: TextOverflow.ellipsis, |
| /// style: new TextStyle(fontWeight: FontWeight.bold), |
| /// ) |
| /// ``` |
| /// |
| /// ## Interactivity |
| /// |
| /// To make [Text] react to touch events, wrap it in a [GestureDetector] widget |
| /// with a [GestureDetector.onTap] handler. |
| /// |
| /// In a material design application, consider using a [FlatButton] instead, or |
| /// if that isn't appropriate, at least using an [InkWell] instead of |
| /// [GestureDetector]. |
| /// |
| /// To make sections of the text interactive, use [RichText] and specify a |
| /// [TapGestureRecognizer] as the [TextSpan.recognizer] of the relevant part of |
| /// the text. |
| /// |
| /// See also: |
| /// |
| /// * [RichText], which gives you more control over the text styles. |
| /// * [DefaultTextStyle], which sets default styles for [Text] widgets. |
| class Text extends StatelessWidget { |
| /// Creates a text widget. |
| /// |
| /// If the [style] argument is null, the text will use the style from the |
| /// closest enclosing [DefaultTextStyle]. |
| const Text(this.data, { |
| Key key, |
| this.style, |
| this.textAlign, |
| this.textDirection, |
| this.softWrap, |
| this.overflow, |
| this.textScaleFactor, |
| this.maxLines, |
| }) : assert(data != null), |
| textSpan = null, |
| super(key: key); |
| |
| /// Creates a text widget with a [TextSpan]. |
| const Text.rich(this.textSpan, { |
| Key key, |
| this.style, |
| this.textAlign, |
| this.textDirection, |
| this.softWrap, |
| this.overflow, |
| this.textScaleFactor, |
| this.maxLines, |
| }): assert(textSpan != null), |
| data = null, |
| super(key: key); |
| |
| /// The text to display. |
| /// |
| /// This will be null if a [textSpan] is provided instead. |
| final String data; |
| |
| /// The text to display as a [TextSpan]. |
| /// |
| /// This will be null if [data] is provided instead. |
| final TextSpan textSpan; |
| |
| /// If non-null, the style to use for this text. |
| /// |
| /// If the style's "inherit" property is true, the style will be merged with |
| /// the closest enclosing [DefaultTextStyle]. Otherwise, the style will |
| /// replace the closest enclosing [DefaultTextStyle]. |
| final TextStyle style; |
| |
| /// How the text should be aligned horizontally. |
| final TextAlign textAlign; |
| |
| /// The directionality of the text. |
| /// |
| /// This decides how [textAlign] values like [TextAlign.start] and |
| /// [TextAlign.end] are interpreted. |
| /// |
| /// This is also used to disambiguate how to render bidirectional text. For |
| /// example, if the [data] is an English phrase followed by a Hebrew phrase, |
| /// in a [TextDirection.ltr] context the English phrase will be on the left |
| /// and the Hebrew phrase to its right, while in a [TextDirection.rtl] |
| /// context, the English phrase will be on the right and the Hebrew phrase on |
| /// its left. |
| /// |
| /// Defaults to the ambient [Directionality], if any. |
| final TextDirection textDirection; |
| |
| /// Whether the text should break at soft line breaks. |
| /// |
| /// If false, the glyphs in the text will be positioned as if there was unlimited horizontal space. |
| final bool softWrap; |
| |
| /// How visual overflow should be handled. |
| final TextOverflow overflow; |
| |
| /// The number of font pixels for each logical pixel. |
| /// |
| /// For example, if the text scale factor is 1.5, text will be 50% larger than |
| /// the specified font size. |
| /// |
| /// The value given to the constructor as textScaleFactor. If null, will |
| /// use the [MediaQueryData.textScaleFactor] obtained from the ambient |
| /// [MediaQuery], or 1.0 if there is no [MediaQuery] in scope. |
| final double textScaleFactor; |
| |
| /// An optional maximum number of lines for the text to span, wrapping if necessary. |
| /// If the text exceeds the given number of lines, it will be truncated according |
| /// to [overflow]. |
| /// |
| /// If this is 1, text will not wrap. Otherwise, text will be wrapped at the |
| /// edge of the box. |
| /// |
| /// If this is null, but there is an ambient [DefaultTextStyle] that specifies |
| /// an explicit number for its [DefaultTextStyle.maxLines], then the |
| /// [DefaultTextStyle] value will take precedence. You can use a [RichText] |
| /// widget directly to entirely override the [DefaultTextStyle]. |
| final int maxLines; |
| |
| @override |
| Widget build(BuildContext context) { |
| final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context); |
| TextStyle effectiveTextStyle = style; |
| if (style == null || style.inherit) |
| effectiveTextStyle = defaultTextStyle.style.merge(style); |
| return new RichText( |
| textAlign: textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start, |
| textDirection: textDirection, // RichText uses Directionality.of to obtain a default if this is null. |
| softWrap: softWrap ?? defaultTextStyle.softWrap, |
| overflow: overflow ?? defaultTextStyle.overflow, |
| textScaleFactor: textScaleFactor ?? MediaQuery.of(context, nullOk: true)?.textScaleFactor ?? 1.0, |
| maxLines: maxLines ?? defaultTextStyle.maxLines, |
| text: new TextSpan( |
| style: effectiveTextStyle, |
| text: data, |
| children: textSpan != null ? <TextSpan>[textSpan] : null, |
| ), |
| ); |
| } |
| |
| @override |
| void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
| super.debugFillProperties(properties); |
| properties.add(new StringProperty('data', data, showName: false)); |
| if (textSpan != null) { |
| properties.add(textSpan.toDiagnosticsNode(name: 'textSpan', style: DiagnosticsTreeStyle.transition)); |
| } |
| style?.debugFillProperties(properties); |
| properties.add(new EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: null)); |
| properties.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null)); |
| properties.add(new FlagProperty('softWrap', value: softWrap, ifTrue: 'wrapping at box width', ifFalse: 'no wrapping except at line break characters', showName: true)); |
| properties.add(new EnumProperty<TextOverflow>('overflow', overflow, defaultValue: null)); |
| properties.add(new DoubleProperty('textScaleFactor', textScaleFactor, defaultValue: null)); |
| properties.add(new IntProperty('maxLines', maxLines, defaultValue: null)); |
| } |
| } |