Commit 17ac7389 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Add MediaQuery.textScaleFactor (#5936)

This feature is the implementation of an accessibility feature that changes the
size of text by a constant factor.

Fixes #5873
parent 3c467a89
......@@ -43,9 +43,9 @@ void checkIconColor(WidgetTester tester, String label, Color color) {
// way to find the menu item. I hope.
Element semantics = findElementOfExactWidgetTypeGoingUp(tester.element(find.text(label)), MergeSemantics);
expect(semantics, isNotNull);
Element asset = findElementOfExactWidgetTypeGoingDown(semantics, Text);
Text text = asset.widget;
expect(text.style.color, equals(color));
Element asset = findElementOfExactWidgetTypeGoingDown(semantics, RichText);
RichText richText = asset.widget;
expect(richText.text.style.color, equals(color));
}
void main() {
......
......@@ -86,13 +86,15 @@ class Icon extends StatelessWidget {
width: iconSize,
height: iconSize,
child: new Center(
child: new Text(
new String.fromCharCode(icon.codePoint),
style: new TextStyle(
inherit: false,
color: iconColor,
fontSize: iconSize,
fontFamily: 'MaterialIcons'
child: new RichText(
text: new TextSpan(
text: new String.fromCharCode(icon.codePoint),
style: new TextStyle(
inherit: false,
color: iconColor,
fontSize: iconSize,
fontFamily: 'MaterialIcons'
)
)
)
)
......
......@@ -251,6 +251,8 @@ class _RenderSlider extends RenderConstrainedBox implements SemanticActionHandle
_label = newLabel;
additionalConstraints = _getAdditionalConstraints(_label);
if (newLabel != null) {
// TODO(abarth): Handle textScaleFactor.
// https://github.com/flutter/flutter/issues/5938
_labelPainter
..text = new TextSpan(
style: Typography.white.body1.copyWith(fontSize: 10.0),
......
......@@ -301,6 +301,8 @@ List<TextPainter> _initPainters(TextTheme textTheme, List<String> labels) {
List<TextPainter> painters = new List<TextPainter>(labels.length);
for (int i = 0; i < painters.length; ++i) {
String label = labels[i];
// TODO(abarth): Handle textScaleFactor.
// https://github.com/flutter/flutter/issues/5939
painters[i] = new TextPainter(
text: new TextSpan(style: style, text: label)
)..layout();
......
......@@ -32,9 +32,11 @@ class TextPainter {
/// [layout].
TextPainter({
TextSpan text,
TextAlign textAlign
}) : _text = text, _textAlign = textAlign {
TextAlign textAlign,
double textScaleFactor: 1.0
}) : _text = text, _textAlign = textAlign, _textScaleFactor = textScaleFactor {
assert(text == null || text.debugAssertIsValid());
assert(textScaleFactor != null);
}
ui.Paragraph _paragraph;
......@@ -63,6 +65,21 @@ class TextPainter {
_needsLayout = true;
}
/// 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.
double get textScaleFactor => _textScaleFactor;
double _textScaleFactor;
set textScaleFactor(double value) {
assert(value != null);
if (_textScaleFactor == value)
return;
_textScaleFactor = value;
_paragraph = null;
_needsLayout = true;
}
// Unfortunately, using full precision floating point here causes bad layouts
// because floating point math isn't associative. If we add and subtract
// padding, for example, we'll get different values when we estimate sizes and
......@@ -144,8 +161,8 @@ class TextPainter {
_needsLayout = false;
if (_paragraph == null) {
ui.ParagraphBuilder builder = new ui.ParagraphBuilder();
_text.build(builder);
ui.ParagraphStyle paragraphStyle = _text.style?.getParagraphStyle(textAlign: textAlign);
_text.build(builder, textScaleFactor: textScaleFactor);
ui.ParagraphStyle paragraphStyle = _text.style?.getParagraphStyle(textAlign: textAlign, textScaleFactor: textScaleFactor);
paragraphStyle ??= new ui.ParagraphStyle();
_paragraph = builder.build(paragraphStyle);
}
......
......@@ -93,17 +93,17 @@ class TextSpan {
/// Rather than using this directly, it's simpler to use the
/// [TextPainter] class to paint [TextSpan] objects onto [Canvas]
/// objects.
void build(ui.ParagraphBuilder builder) {
void build(ui.ParagraphBuilder builder, { double textScaleFactor: 1.0 }) {
assert(debugAssertIsValid());
final bool hasStyle = style != null;
if (hasStyle)
builder.pushStyle(style.textStyle);
builder.pushStyle(style.getTextStyle(textScaleFactor: textScaleFactor));
if (text != null)
builder.addText(text);
if (children != null) {
for (TextSpan child in children) {
assert(child != null);
child.build(builder);
child.build(builder, textScaleFactor: textScaleFactor);
}
}
if (hasStyle)
......
......@@ -35,6 +35,10 @@ class TextStyle {
final String fontFamily;
/// The size of gyphs (in logical pixels) to use when painting the text.
///
/// During painting, the [fontSize] is multiplied by the current
/// `textScaleFactor` to let users make it easier to read text by increasing
/// its size.
final double fontSize;
/// The typeface thickness to use when painting the text (e.g., bold).
......@@ -207,7 +211,7 @@ class TextStyle {
}
/// The style information for text runs, encoded for use by `dart:ui`.
ui.TextStyle get textStyle {
ui.TextStyle getTextStyle({ double textScaleFactor: 1.0 }) {
return new ui.TextStyle(
color: color,
decoration: decoration,
......@@ -217,7 +221,7 @@ class TextStyle {
fontStyle: fontStyle,
textBaseline: textBaseline,
fontFamily: fontFamily,
fontSize: fontSize,
fontSize: fontSize == null ? null : fontSize * textScaleFactor,
letterSpacing: letterSpacing,
wordSpacing: wordSpacing,
height: height
......@@ -225,13 +229,13 @@ class TextStyle {
}
/// The style information for paragraphs, encoded for use by `dart:ui`.
ui.ParagraphStyle getParagraphStyle({ TextAlign textAlign }) {
ui.ParagraphStyle getParagraphStyle({ TextAlign textAlign, double textScaleFactor: 1.0 }) {
return new ui.ParagraphStyle(
textAlign: textAlign,
fontWeight: fontWeight,
fontStyle: fontStyle,
fontFamily: fontFamily,
fontSize: fontSize,
fontSize: fontSize == null ? null : fontSize * textScaleFactor,
lineHeight: height
);
}
......
......@@ -44,11 +44,12 @@ class RenderEditableLine extends RenderBox {
Color cursorColor,
bool showCursor: false,
Color selectionColor,
double textScaleFactor: 1.0,
TextSelection selection,
this.onSelectionChanged,
Offset paintOffset: Offset.zero,
this.onPaintOffsetUpdateNeeded
}) : _textPainter = new TextPainter(text: text),
}) : _textPainter = new TextPainter(text: text, textScaleFactor: textScaleFactor),
_cursorColor = cursorColor,
_showCursor = showCursor,
_selection = selection,
......@@ -111,6 +112,19 @@ class RenderEditableLine extends RenderBox {
markNeedsPaint();
}
/// 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.
double get textScaleFactor => _textPainter.textScaleFactor;
set textScaleFactor(double value) {
assert(value != null);
if (_textPainter.textScaleFactor == value)
return;
_textPainter.textScaleFactor = value;
markNeedsLayout();
}
List<ui.TextBox> _selectionRects;
/// The region of text that is selected, if any.
......@@ -181,7 +195,7 @@ class RenderEditableLine extends RenderBox {
double get _preferredHeight {
if (_layoutTemplate == null) {
ui.ParagraphBuilder builder = new ui.ParagraphBuilder()
..pushStyle(text.style.textStyle)
..pushStyle(text.style.getTextStyle(textScaleFactor: textScaleFactor))
..addText(_kZeroWidthSpace);
// TODO(abarth): ParagraphBuilder#build's argument should be optional.
// TODO(abarth): These min/max values should be the default for ui.Paragraph.
......
......@@ -29,15 +29,17 @@ class RenderParagraph extends RenderBox {
/// The [text], [overflow], and [softWrap] arguments must not be null.
RenderParagraph(TextSpan text, {
TextAlign textAlign,
bool softWrap: true,
TextOverflow overflow: TextOverflow.clip,
bool softWrap: true
double textScaleFactor: 1.0
}) : _softWrap = softWrap,
_overflow = overflow,
_textPainter = new TextPainter(text: text, textAlign: textAlign) {
_textPainter = new TextPainter(text: text, textAlign: textAlign, textScaleFactor: textScaleFactor) {
assert(text != null);
assert(text.debugAssertIsValid());
assert(overflow != null);
assert(softWrap != null);
assert(overflow != null);
assert(textScaleFactor != null);
}
final TextPainter _textPainter;
......@@ -87,6 +89,21 @@ class RenderParagraph extends RenderBox {
markNeedsPaint();
}
/// 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.
double get textScaleFactor => _textPainter.textScaleFactor;
set textScaleFactor(double value) {
assert(value != null);
if (_textPainter.textScaleFactor == value)
return;
_textPainter.textScaleFactor = value;
_overflowPainter = null;
_overflowShader = null;
markNeedsLayout();
}
void _layoutText({ double minWidth: 0.0, double maxWidth: double.INFINITY }) {
_textPainter.layout(minWidth: minWidth, maxWidth: _softWrap ? maxWidth : double.INFINITY);
}
......@@ -171,7 +188,8 @@ class RenderParagraph extends RenderBox {
case TextOverflow.fade:
case TextOverflow.ellipsis:
_overflowPainter ??= new TextPainter(
text: new TextSpan(style: _textPainter.text.style, text: '\u2026')
text: new TextSpan(style: _textPainter.text.style, text: '\u2026'),
textScaleFactor: textScaleFactor
)..layout();
final double overflowUnit = _overflowPainter.width;
double fadeEnd = size.width;
......
......@@ -18,6 +18,7 @@ import 'media_query.dart';
import 'navigator.dart';
import 'performance_overlay.dart';
import 'semantics_debugger.dart';
import 'text.dart';
import 'title.dart';
/// Signature for a function that is called when the operating system changes the current locale.
......@@ -183,7 +184,7 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
)
);
if (config.textStyle != null) {
new DefaultTextStyle(
result = new DefaultTextStyle(
style: config.textStyle,
child: result
);
......
......@@ -2128,11 +2128,13 @@ class RichText extends LeafRenderObjectWidget {
@required this.text,
this.textAlign,
this.softWrap: true,
this.overflow: TextOverflow.clip
this.overflow: TextOverflow.clip,
this.textScaleFactor: 1.0
}) : super(key: key) {
assert(text != null);
assert(softWrap != null);
assert(overflow != null);
assert(textScaleFactor != null);
}
/// The text to display in this widget.
......@@ -2149,12 +2151,19 @@ class RichText extends LeafRenderObjectWidget {
/// 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.
final double textScaleFactor;
@override
RenderParagraph createRenderObject(BuildContext context) {
return new RenderParagraph(text,
textAlign: textAlign,
softWrap: softWrap,
overflow: overflow
overflow: overflow,
textScaleFactor: textScaleFactor
);
}
......@@ -2164,178 +2173,8 @@ class RichText extends LeafRenderObjectWidget {
..text = text
..textAlign = textAlign
..softWrap = softWrap
..overflow = overflow;
}
}
/// 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.inherit] to inherit styling information
/// from a the current default text style for a given [BuildContext].
DefaultTextStyle({
Key key,
@required this.style,
this.textAlign,
this.softWrap: true,
this.overflow: TextOverflow.clip,
Widget child
}) : super(key: key, child: child) {
assert(style != null);
assert(softWrap != null);
assert(overflow != null);
assert(child != null);
}
/// 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.
const DefaultTextStyle.fallback()
: style = const TextStyle(),
textAlign = null,
softWrap = true,
overflow = TextOverflow.clip;
/// Creates a default text style that inherits from the given [BuildContext].
///
/// The given [style] is merged with the [style] from the default text style
/// for the given [BuildContext] and, if non-null, the given [textAlign]
/// replaces the [textAlign] from the default text style for the given
/// [BuildContext].
factory DefaultTextStyle.inherit({
Key key,
@required BuildContext context,
TextStyle style,
TextAlign textAlign,
bool softWrap,
TextOverflow overflow,
Widget child
}) {
assert(context != null);
assert(child != null);
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,
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;
/// 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.
static DefaultTextStyle of(BuildContext context) {
return context.inheritFromWidgetOfExactType(DefaultTextStyle) ?? const DefaultTextStyle.fallback();
}
@override
bool updateShouldNotify(DefaultTextStyle old) => style != old.style;
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
'$style'.split('\n').forEach(description.add);
}
}
/// 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.
///
/// To display text that uses multiple styles (e.g., a paragraph with some bold
/// words), use [RichText].
///
/// See also:
///
/// * [RichText]
/// * [DefaultTextStyle]
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].
Text(this.data, {
Key key,
this.style,
this.textAlign,
this.softWrap,
this.overflow
}) : super(key: key) {
assert(data != null);
}
/// The text to display.
final String data;
/// 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;
/// 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;
@override
Widget build(BuildContext context) {
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,
softWrap: softWrap ?? defaultTextStyle.softWrap,
overflow: overflow ?? defaultTextStyle.overflow,
text: new TextSpan(
style: effectiveTextStyle,
text: data
)
);
}
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('"$data"');
if (style != null)
'$style'.split('\n').forEach(description.add);
..overflow = overflow
..textScaleFactor = textScaleFactor;
}
}
......
......@@ -10,10 +10,11 @@ import 'package:meta/meta.dart';
import 'package:sky_services/editing/editing.mojom.dart' as mojom;
import 'basic.dart';
import 'framework.dart';
import 'focus.dart';
import 'scrollable.dart';
import 'framework.dart';
import 'media_query.dart';
import 'scroll_behavior.dart';
import 'scrollable.dart';
import 'text_selection.dart';
export 'package:flutter/painting.dart' show TextSelection;
......@@ -166,6 +167,7 @@ class RawInputLine extends Scrollable {
this.hideText: false,
this.style,
this.cursorColor,
this.textScaleFactor,
this.selectionColor,
this.selectionHandleBuilder,
this.selectionToolbarBuilder,
......@@ -193,6 +195,14 @@ class RawInputLine extends Scrollable {
/// The text style to use for the editable text.
final TextStyle style;
/// 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.
///
/// Defaults to [MediaQuery.textScaleFactor].
final double textScaleFactor;
/// The color to use when painting the cursor.
final Color cursorColor;
......@@ -438,6 +448,7 @@ class RawInputLineState extends ScrollableState<RawInputLine> {
cursorColor: config.cursorColor,
showCursor: _showCursor,
selectionColor: config.selectionColor,
textScaleFactor: config.textScaleFactor ?? MediaQuery.of(context).textScaleFactor,
hideText: config.hideText,
onSelectionChanged: _handleSelectionChanged,
paintOffset: scrollOffsetToPixelDelta(scrollOffset),
......@@ -454,6 +465,7 @@ class _EditableLineWidget extends LeafRenderObjectWidget {
this.cursorColor,
this.showCursor,
this.selectionColor,
this.textScaleFactor,
this.hideText,
this.onSelectionChanged,
this.paintOffset,
......@@ -465,6 +477,7 @@ class _EditableLineWidget extends LeafRenderObjectWidget {
final Color cursorColor;
final bool showCursor;
final Color selectionColor;
final double textScaleFactor;
final bool hideText;
final SelectionChangedHandler onSelectionChanged;
final Offset paintOffset;
......@@ -477,6 +490,7 @@ class _EditableLineWidget extends LeafRenderObjectWidget {
cursorColor: cursorColor,
showCursor: showCursor,
selectionColor: selectionColor,
textScaleFactor: textScaleFactor,
selection: value.selection,
onSelectionChanged: onSelectionChanged,
paintOffset: paintOffset,
......@@ -491,6 +505,7 @@ class _EditableLineWidget extends LeafRenderObjectWidget {
..cursorColor = cursorColor
..showCursor = showCursor
..selectionColor = selectionColor
..textScaleFactor = textScaleFactor
..selection = value.selection
..onSelectionChanged = onSelectionChanged
..paintOffset = paintOffset
......
......@@ -8,6 +8,7 @@ import 'package:vector_math/vector_math_64.dart';
import 'basic.dart';
import 'container.dart';
import 'framework.dart';
import 'text.dart';
/// An interpolation between two [BoxConstraint]s.
class BoxConstraintsTween extends Tween<BoxConstraints> {
......
......@@ -31,12 +31,18 @@ class MediaQueryData {
///
/// Consider using [MediaQueryData.fromWindow] to create data based on a
/// [ui.Window].
const MediaQueryData({ this.size, this.devicePixelRatio, this.padding });
const MediaQueryData({
this.size: Size.zero,
this.devicePixelRatio: 1.0,
this.textScaleFactor: 1.0,
this.padding: EdgeInsets.zero
});
/// Creates data for a media query based on the given window.
MediaQueryData.fromWindow(ui.Window window)
: size = window.physicalSize / window.devicePixelRatio,
devicePixelRatio = window.devicePixelRatio,
textScaleFactor = 1.0, // TODO(abarth): Read this value from window.
padding = new EdgeInsets.fromWindowPadding(window.padding, window.devicePixelRatio);
/// The size of the media in logical pixel (e.g, the size of the screen).
......@@ -52,6 +58,12 @@ class MediaQueryData {
/// the Nexus 6 has a device pixel ratio of 3.5.
final double devicePixelRatio;
/// 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.
final double textScaleFactor;
/// The padding around the edges of the media (e.g., the screen).
final EdgeInsets padding;
......@@ -66,19 +78,16 @@ class MediaQueryData {
return false;
MediaQueryData typedOther = other;
return typedOther.size == size
&& typedOther.padding == padding
&& typedOther.devicePixelRatio == devicePixelRatio;
&& typedOther.devicePixelRatio == devicePixelRatio
&& typedOther.textScaleFactor == textScaleFactor
&& typedOther.padding == padding;
}
@override
int get hashCode => hashValues(
size.hashCode,
padding.hashCode,
devicePixelRatio.hashCode
);
int get hashCode => hashValues(size, devicePixelRatio, textScaleFactor, padding);
@override
String toString() => '$runtimeType(size: $size, devicePixelRatio: $devicePixelRatio, padding: $padding)';
String toString() => '$runtimeType(size: $size, devicePixelRatio: $devicePixelRatio, textScaleFactor: $textScaleFactor, padding: $padding)';
}
/// Establishes a subtree in which media queries resolve to the given data.
......
// 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:meta/meta.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.inherit] to inherit styling information
/// from a the current default text style for a given [BuildContext].
DefaultTextStyle({
Key key,
@required this.style,
this.textAlign,
this.softWrap: true,
this.overflow: TextOverflow.clip,
Widget child
}) : super(key: key, child: child) {
assert(style != null);
assert(softWrap != null);
assert(overflow != null);
assert(child != null);
}
/// 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.
const DefaultTextStyle.fallback()
: style = const TextStyle(),
textAlign = null,
softWrap = true,
overflow = TextOverflow.clip;
/// Creates a default text style that inherits from the given [BuildContext].
///
/// The given [style] is merged with the [style] from the default text style
/// for the given [BuildContext] and, if non-null, the given [textAlign]
/// replaces the [textAlign] from the default text style for the given
/// [BuildContext].
factory DefaultTextStyle.inherit({
Key key,
@required BuildContext context,
TextStyle style,
TextAlign textAlign,
bool softWrap,
TextOverflow overflow,
Widget child
}) {
assert(context != null);
assert(child != null);
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,
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;
/// 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.
static DefaultTextStyle of(BuildContext context) {
return context.inheritFromWidgetOfExactType(DefaultTextStyle) ?? const DefaultTextStyle.fallback();
}
@override
bool updateShouldNotify(DefaultTextStyle old) => style != old.style;
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
'$style'.split('\n').forEach(description.add);
}
}
/// 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.
///
/// To display text that uses multiple styles (e.g., a paragraph with some bold
/// words), use [RichText].
///
/// See also:
///
/// * [RichText]
/// * [DefaultTextStyle]
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].
Text(this.data, {
Key key,
this.style,
this.textAlign,
this.softWrap,
this.overflow,
this.textScaleFactor
}) : super(key: key) {
assert(data != null);
}
/// The text to display.
final String data;
/// 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;
/// 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.
///
/// Defaults to [MediaQuery.textScaleFactor].
final double textScaleFactor;
@override
Widget build(BuildContext context) {
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,
softWrap: softWrap ?? defaultTextStyle.softWrap,
overflow: overflow ?? defaultTextStyle.overflow,
textScaleFactor: textScaleFactor ?? MediaQuery.of(context).textScaleFactor,
text: new TextSpan(
style: effectiveTextStyle,
text: data
)
);
}
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('"$data"');
if (style != null)
'$style'.split('\n').forEach(description.add);
}
}
......@@ -56,6 +56,7 @@ export 'src/widgets/size_changed_layout_notifier.dart';
export 'src/widgets/status_transitions.dart';
export 'src/widgets/table.dart';
export 'src/widgets/text_selection.dart';
export 'src/widgets/text.dart';
export 'src/widgets/title.dart';
export 'src/widgets/transitions.dart';
export 'src/widgets/unique_widget.dart';
......
......@@ -89,10 +89,10 @@ void main() {
expect(s5.height, 123.0);
expect(s5.color, isNull);
ui.TextStyle ts5 = s5.textStyle;
ui.TextStyle ts5 = s5.getTextStyle();
expect(ts5, equals(new ui.TextStyle(fontWeight: FontWeight.w700, fontSize: 12.0, height: 123.0)));
expect(ts5.toString(), 'TextStyle(color: unspecified, decoration: unspecified, decorationColor: unspecified, decorationStyle: unspecified, fontWeight: FontWeight.w700, fontStyle: unspecified, textBaseline: unspecified, fontFamily: unspecified, fontSize: 12.0, letterSpacing: unspecified, wordSpacing: unspecified, height: 123.0x)');
ui.TextStyle ts2 = s2.textStyle;
ui.TextStyle ts2 = s2.getTextStyle();
expect(ts2, equals(new ui.TextStyle(color: const Color(0xFF00FF00), fontWeight: FontWeight.w800, fontSize: 10.0, height: 100.0)));
expect(ts2.toString(), 'TextStyle(color: Color(0xff00ff00), decoration: unspecified, decorationColor: unspecified, decorationStyle: unspecified, fontWeight: FontWeight.w800, fontStyle: unspecified, textBaseline: unspecified, fontFamily: unspecified, fontSize: 10.0, letterSpacing: unspecified, wordSpacing: unspecified, height: 100.0x)');
......
......@@ -16,7 +16,7 @@ void main() {
child: new Icon(Icons.add)
)
);
Text text = tester.widget(find.byType(Text));
expect(text.style.color, equals(Colors.green[500].withOpacity(0.5)));
RichText text = tester.widget(find.byType(RichText));
expect(text.text.style.color, equals(Colors.green[500].withOpacity(0.5)));
});
}
......@@ -2,9 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/src/widgets/basic.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter/src/widgets/layout_builder.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart' hide TypeMatcher;
// This is a regression test for https://github.com/flutter/flutter/issues/5588.
......
// 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_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
void main() {
testWidgets('Text respects media query', (WidgetTester tester) async {
await tester.pumpWidget(new MediaQuery(
data: new MediaQueryData(textScaleFactor: 1.5),
child: new Center(
child: new Text('Hello')
)
));
RichText text = tester.firstWidget(find.byType(RichText));
expect(text, isNotNull);
expect(text.textScaleFactor, 1.5);
await tester.pumpWidget(new Center(
child: new Text('Hello')
));
text = tester.firstWidget(find.byType(RichText));
expect(text, isNotNull);
expect(text.textScaleFactor, 1.0);
await tester.pumpWidget(new Center(
child: new Text('Hello', textScaleFactor: 3.0)
));
text = tester.firstWidget(find.byType(RichText));
expect(text, isNotNull);
expect(text.textScaleFactor, 3.0);
});
}
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