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