blob: b1425e0ee0c293633ea1be5abb935c4963007996 [file] [log] [blame]
// 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));
}
}