TextPainter RTL (#11888)

This commit is contained in:
Ian Hickson 2017-09-07 16:57:38 -07:00 committed by GitHub
parent f4ccb4b6a4
commit ca7d2d23cf
144 changed files with 2223 additions and 1019 deletions

View File

@ -1 +1 @@
ccf68cdcb66f9fe354bf3e8472bb0b47c83e8ac9
2d7c30033d76fda9779462827ad5322d78e1fb3a

View File

@ -0,0 +1,7 @@
```
# To run the Hello World demo:
flutter run
# To run the Hello World demo showing Arabic:
flutter run lib/arabic.dart
```

View File

@ -0,0 +1,7 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/widgets.dart';
void main() => runApp(const Center(child: const Text('برنامج أهلا بالعالم', textDirection: TextDirection.rtl)));

View File

@ -4,4 +4,4 @@
import 'package:flutter/widgets.dart';
void main() => runApp(const Center(child: const Text('Hello, world!')));
void main() => runApp(const Center(child: const Text('Hello, world!', textDirection: TextDirection.ltr)));

View File

@ -4,4 +4,4 @@
import 'package:flutter/widgets.dart';
void main() => runApp(const Center(child: const Text('flutter run -t xxx/yyy.dart')));
void main() => runApp(const Center(child: const Text('flutter run -t xxx/yyy.dart', textDirection: TextDirection.ltr)));

View File

@ -11,7 +11,9 @@ void beginFrame(Duration timeStamp) {
final double devicePixelRatio = ui.window.devicePixelRatio;
final ui.Size logicalSize = ui.window.physicalSize / devicePixelRatio;
final ui.ParagraphBuilder paragraphBuilder = new ui.ParagraphBuilder(new ui.ParagraphStyle())
final ui.ParagraphBuilder paragraphBuilder = new ui.ParagraphBuilder(
new ui.ParagraphStyle(textDirection: ui.TextDirection.ltr),
)
..addText('Hello, world.');
final ui.Paragraph paragraph = paragraphBuilder.build()
..layout(new ui.ParagraphConstraints(width: logicalSize.width));

View File

@ -52,7 +52,13 @@ void beginFrame(Duration timeStamp) {
void main() {
// To create a paragraph of text, we use ParagraphBuilder.
final ui.ParagraphBuilder builder = new ui.ParagraphBuilder(new ui.ParagraphStyle())
final ui.ParagraphBuilder builder = new ui.ParagraphBuilder(
// The text below has a primary direction of left-to-right.
// The embedded text has other directions.
// If this was TextDirection.rtl, the "Hello, world" text would end up on
// the other side of the right-to-left text.
new ui.ParagraphStyle(textDirection: ui.TextDirection.ltr),
)
// We first push a style that turns the text blue.
..pushStyle(new ui.TextStyle(color: const ui.Color(0xFF0000FF)))
..addText('Hello, ')

View File

@ -14,24 +14,36 @@ void main() {
void addAlignmentRow(CrossAxisAlignment crossAxisAlignment) {
TextStyle style = const TextStyle(color: const Color(0xFF000000));
final RenderParagraph paragraph = new RenderParagraph(new TextSpan(style: style, text: '$crossAxisAlignment'));
final RenderParagraph paragraph = new RenderParagraph(
new TextSpan(style: style, text: '$crossAxisAlignment'),
textDirection: TextDirection.ltr,
);
table.add(new RenderPadding(child: paragraph, padding: const EdgeInsets.only(top: 20.0)));
final RenderFlex row = new RenderFlex(crossAxisAlignment: crossAxisAlignment, textBaseline: TextBaseline.alphabetic);
style = const TextStyle(fontSize: 15.0, color: const Color(0xFF000000));
row.add(new RenderDecoratedBox(
decoration: const BoxDecoration(color: const Color(0x7FFFCCCC)),
child: new RenderParagraph(new TextSpan(style: style, text: 'foo foo foo'))
child: new RenderParagraph(
new TextSpan(style: style, text: 'foo foo foo'),
textDirection: TextDirection.ltr,
),
));
style = const TextStyle(fontSize: 10.0, color: const Color(0xFF000000));
row.add(new RenderDecoratedBox(
decoration: const BoxDecoration(color: const Color(0x7FCCFFCC)),
child: new RenderParagraph(new TextSpan(style: style, text: 'foo foo foo'))
child: new RenderParagraph(
new TextSpan(style: style, text: 'foo foo foo'),
textDirection: TextDirection.ltr,
),
));
final RenderFlex subrow = new RenderFlex(crossAxisAlignment: crossAxisAlignment, textBaseline: TextBaseline.alphabetic);
style = const TextStyle(fontSize: 25.0, color: const Color(0xFF000000));
subrow.add(new RenderDecoratedBox(
decoration: const BoxDecoration(color: const Color(0x7FCCCCFF)),
child: new RenderParagraph(new TextSpan(style: style, text: 'foo foo foo foo'))
child: new RenderParagraph(
new TextSpan(style: style, text: 'foo foo foo foo'),
textDirection: TextDirection.ltr,
),
));
subrow.add(new RenderSolidColorBox(const Color(0x7FCCFFFF), desiredSize: const Size(30.0, 40.0)));
row.add(subrow);
@ -48,7 +60,10 @@ void main() {
void addJustificationRow(MainAxisAlignment justify) {
const TextStyle style = const TextStyle(color: const Color(0xFF000000));
final RenderParagraph paragraph = new RenderParagraph(new TextSpan(style: style, text: '$justify'));
final RenderParagraph paragraph = new RenderParagraph(
new TextSpan(style: style, text: '$justify'),
textDirection: TextDirection.ltr,
);
table.add(new RenderPadding(child: paragraph, padding: const EdgeInsets.only(top: 20.0)));
final RenderFlex row = new RenderFlex(direction: Axis.horizontal);
row.add(new RenderSolidColorBox(const Color(0xFFFFCCCC), desiredSize: const Size(80.0, 60.0)));

View File

@ -16,7 +16,14 @@ void main() {
alignment: FractionalOffset.center,
// We use a RenderParagraph to display the text 'Hello, world.' without
// any explicit styling.
child: new RenderParagraph(const TextSpan(text: 'Hello, world.'))
child: new RenderParagraph(
const TextSpan(text: 'Hello, world.'),
// The text is in English so we specify the text direction as
// left-to-right. If the text had been in Hebrew or Arabic, we would
// have specified right-to-left. The Flutter framework does not assume a
// particular text direction.
textDirection: TextDirection.ltr,
),
)
);
}

View File

@ -104,8 +104,9 @@ void main() {
final RenderParagraph paragraph = new RenderParagraph(
const TextSpan(
style: const TextStyle(color: Colors.black87),
text: "Touch me!"
)
text: "Touch me!",
),
textDirection: TextDirection.ltr,
);
// A stack is a render object that layers its children on top of each other.
// The bottom later is our RenderDots object, and on top of that we show the
@ -114,7 +115,7 @@ void main() {
children: <RenderBox>[
new RenderDots(),
paragraph,
]
],
);
// The "parentData" field of a render object is controlled by the render
// object's parent render object. Now that we've added the paragraph as a

View File

@ -0,0 +1,13 @@
// Copyright 2017 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:test/test.dart';
import '../../../lib/main.dart' as demo;
void main() {
test('layers smoketest for lib/main.dart', () {
demo.main();
});
}

View File

@ -0,0 +1,13 @@
// Copyright 2017 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:test/test.dart';
import '../../../raw/canvas.dart' as demo;
void main() {
test('layers smoketest for raw/canvas.dart', () {
demo.main();
});
}

View File

@ -0,0 +1,13 @@
// Copyright 2017 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:test/test.dart';
import '../../../raw/hello_world.dart' as demo;
void main() {
test('layers smoketest for raw/hello_world.dart', () {
demo.main();
});
}

View File

@ -0,0 +1,13 @@
// Copyright 2017 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:test/test.dart';
import '../../../raw/spinning_square.dart' as demo;
void main() {
test('layers smoketest for raw/spinning_square.dart', () {
demo.main();
});
}

View File

@ -0,0 +1,13 @@
// Copyright 2017 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:test/test.dart';
import '../../../raw/text.dart' as demo;
void main() {
test('layers smoketest for raw/text.dart', () {
demo.main();
});
}

View File

@ -0,0 +1,13 @@
// Copyright 2017 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:test/test.dart';
import '../../../raw/touch_input.dart' as demo;
void main() {
test('layers smoketest for raw/touch_input.dart', () {
demo.main();
});
}

View File

@ -0,0 +1,13 @@
// Copyright 2017 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:test/test.dart';
import '../../../rendering/custom_coordinate_systems.dart' as demo;
void main() {
test('layers smoketest for rendering/custom_coordinate_systems.dart', () {
demo.main();
});
}

View File

@ -0,0 +1,13 @@
// Copyright 2017 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:test/test.dart';
import '../../../rendering/flex_layout.dart' as demo;
void main() {
test('layers smoketest for rendering/flex_layout.dart', () {
demo.main();
});
}

View File

@ -0,0 +1,13 @@
// Copyright 2017 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:test/test.dart';
import '../../../rendering/hello_world.dart' as demo;
void main() {
test('layers smoketest for rendering/hello_world.dart', () {
demo.main();
});
}

View File

@ -0,0 +1,13 @@
// Copyright 2017 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:test/test.dart';
import '../../../rendering/spinning_square.dart' as demo;
void main() {
test('layers smoketest for rendering/spinning_square.dart', () {
demo.main();
});
}

View File

@ -0,0 +1,13 @@
// Copyright 2017 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:test/test.dart';
import '../../../rendering/touch_input.dart' as demo;
void main() {
test('layers smoketest for rendering/touch_input.dart', () {
demo.main();
});
}

View File

@ -0,0 +1,13 @@
// Copyright 2017 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:test/test.dart';
import '../../../services/isolate.dart' as demo;
void main() {
test('layers smoketest for services/isolate.dart', () {
demo.main();
});
}

View File

@ -0,0 +1,13 @@
// Copyright 2017 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:test/test.dart';
import '../../../services/lifecycle.dart' as demo;
void main() {
test('layers smoketest for services/lifecycle.dart', () {
demo.main();
});
}

View File

@ -0,0 +1,13 @@
// Copyright 2017 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:test/test.dart';
import '../../../widgets/custom_render_box.dart' as demo;
void main() {
test('layers smoketest for widgets/custom_render_box.dart', () {
demo.main();
});
}

View File

@ -0,0 +1,13 @@
// Copyright 2017 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:test/test.dart';
import '../../../widgets/gestures.dart' as demo;
void main() {
test('layers smoketest for widgets/gestures.dart', () {
demo.main();
});
}

View File

@ -0,0 +1,13 @@
// Copyright 2017 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:test/test.dart';
import '../../../widgets/hello_world.dart' as demo;
void main() {
test('layers smoketest for widgets/hello_world.dart', () {
demo.main();
});
}

View File

@ -0,0 +1,13 @@
// Copyright 2017 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:test/test.dart';
import '../../../widgets/media_query.dart' as demo;
void main() {
test('layers smoketest for widgets/media_query.dart', () {
demo.main();
});
}

View File

@ -0,0 +1,13 @@
// Copyright 2017 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:test/test.dart';
import '../../../widgets/sectors.dart' as demo;
void main() {
test('layers smoketest for widgets/sectors.dart', () {
demo.main();
});
}

View File

@ -0,0 +1,13 @@
// Copyright 2017 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:test/test.dart';
import '../../../widgets/spinning_mixed.dart' as demo;
void main() {
test('layers smoketest for widgets/spinning_mixed.dart', () {
demo.main();
});
}

View File

@ -0,0 +1,13 @@
// Copyright 2017 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:test/test.dart';
import '../../../widgets/spinning_square.dart' as demo;
void main() {
test('layers smoketest for widgets/spinning_square.dart', () {
demo.main();
});
}

View File

@ -0,0 +1,13 @@
// Copyright 2017 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:test/test.dart';
import '../../../widgets/styled_text.dart' as demo;
void main() {
test('layers smoketest for widgets/styled_text.dart', () {
demo.main();
});
}

View File

@ -4,4 +4,4 @@
import 'package:flutter/widgets.dart';
void main() => runApp(const Center(child: const Text('Hello, world!')));
void main() => runApp(const Center(child: const Text('Hello, world!', textDirection: TextDirection.ltr)));

View File

@ -37,6 +37,8 @@ BuildOwner owner = new BuildOwner();
void attachWidgetTreeToRenderTree(RenderProxyBox container) {
element = new RenderObjectToWidgetAdapter<RenderBox>(
container: container,
child: new Directionality(
textDirection: TextDirection.ltr,
child: new Container(
height: 300.0,
child: new Column(
@ -53,24 +55,25 @@ void attachWidgetTreeToRenderTree(RenderProxyBox container) {
children: <Widget>[
new Image.network('https://flutter.io/images/favicon.png'),
const Text('PRESS ME'),
]
],
),
onPressed: () {
value = value == null ? 0.1 : (value + 0.1) % 1.0;
attachWidgetTreeToRenderTree(container);
}
},
),
new CircularProgressIndicator(value: value),
],
mainAxisAlignment: MainAxisAlignment.spaceAround
)
)
mainAxisAlignment: MainAxisAlignment.spaceAround,
),
),
),
const Rectangle(const Color(0xFFFFFF00)),
],
mainAxisAlignment: MainAxisAlignment.spaceBetween
)
)
mainAxisAlignment: MainAxisAlignment.spaceBetween,
),
),
),
).attachToRenderTree(owner, element);
}

View File

@ -46,3 +46,4 @@ export 'src/foundation/print.dart';
export 'src/foundation/profile.dart';
export 'src/foundation/serialization.dart';
export 'src/foundation/synchronous_future.dart';
export 'src/foundation/unicode.dart';

View File

@ -0,0 +1,98 @@
// Copyright 2017 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.
/// Constants for useful Unicode characters.
///
/// Currently, these characters are all related to bidirectional text.
///
/// See also:
///
/// * <http://unicode.org/reports/tr9/>, which describes the Unicode
/// bidirectional text algorithm.
class Unicode {
Unicode._();
/// U+202A LEFT-TO-RIGHT EMBEDDING
///
/// Treat the following text as embedded left-to-right.
///
/// Use [PDF] to end the embedding.
static const String LRE = '\u202A';
/// U+202B RIGHT-TO-LEFT EMBEDDING
///
/// Treat the following text as embedded right-to-left.
///
/// Use [PDF] to end the embedding.
static const String RLE = '\u202B';
/// U+202C POP DIRECTIONAL FORMATTING
///
/// End the scope of the last [LRE], [RLE], [RLO], or [LRO].
static const String PDF = '\u202C';
/// U+202A LEFT-TO-RIGHT OVERRIDE
///
/// Force following characters to be treated as strong left-to-right characters.
///
/// For example, this causes Hebrew text to render backwards.
///
/// Use [PDF] to end the override.
static const String LRO = '\u202D';
/// U+202B RIGHT-TO-LEFT OVERRIDE
///
/// Force following characters to be treated as strong right-to-left characters.
///
/// For example, this causes English text to render backwards.
///
/// Use [PDF] to end the override.
static const String RLO = '\u202E';
/// U+2066 LEFT-TO-RIGHT ISOLATE
///
/// Treat the following text as isolated and left-to-right.
///
/// Use [PDI] to end the isolated scope.
static const String LRI = '\u2066';
/// U+2067 RIGHT-TO-LEFT ISOLATE
///
/// Treat the following text as isolated and right-to-left.
///
/// Use [PDI] to end the isolated scope.
static const String RLI = '\u2067';
/// U+2068 FIRST STRONG ISOLATE
///
/// Treat the following text as isolated and in the direction of its first
/// strong directional character that is not inside a nested isolate.
///
/// This essentially "autodetects" the directionality of the text. It is not
/// 100% reliable. For example, Arabic text that starts with an English quote
/// will be detected as LTR, not RTL, which will lead to the text being in a
/// weird order.
///
/// Use [PDI] to end the isolated scope.
static const String FSI = '\u2068';
/// U+2069 POP DIRECTIONAL ISOLATE
///
/// End the scope of the last [LRI], [RLI], or [FSI].
static const String PDI = '\u2069';
/// U+200E LEFT-TO-RIGHT MARK
///
/// Left-to-right zero-width character.
static const String LRM = '\u200E';
/// U+200F RIGHT-TO-LEFT MARK
///
/// Right-to-left zero-width non-Arabic character.
static const String RLM = '\u200F';
/// U+061C ARABIC LETTER MARK
///
/// Right-to-left zero-width Arabic character.
static const String ALM = '\u061C';
}

View File

@ -295,6 +295,7 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler {
@required TextDirection textDirection,
}) : assert(value != null && value >= 0.0 && value <= 1.0),
assert(textDirection != null),
_label = label,
_value = value,
_divisions = divisions,
_activeColor = activeColor,
@ -303,7 +304,7 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler {
_textTheme = textTheme,
_onChanged = onChanged,
_textDirection = textDirection {
this.label = label;
_updateLabelPainter();
final GestureArenaTeam team = new GestureArenaTeam();
_drag = new HorizontalDragGestureRecognizer()
..team = team
@ -356,19 +357,7 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler {
if (value == _label)
return;
_label = value;
if (value != null) {
// TODO(abarth): Handle textScaleFactor.
// https://github.com/flutter/flutter/issues/5938
_labelPainter
..text = new TextSpan(
style: _textTheme.body1.copyWith(fontSize: 10.0),
text: value
)
..layout();
} else {
_labelPainter.text = null;
}
markNeedsLayout();
_updateLabelPainter();
}
Color get activeColor => _activeColor;
@ -424,11 +413,29 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler {
TextDirection _textDirection;
set textDirection(TextDirection value) {
assert(value != null);
if (_textDirection == value)
if (value == _textDirection)
return;
_textDirection = value;
// TODO(abarth): Update _labelPainter's text direction.
markNeedsPaint();
_updateLabelPainter();
}
void _updateLabelPainter() {
if (label != null) {
// TODO(abarth): Handle textScaleFactor. https://github.com/flutter/flutter/issues/5938
_labelPainter
..text = new TextSpan(
style: _textTheme.body1.copyWith(fontSize: 10.0),
text: label,
)
..textDirection = textDirection
..layout();
} else {
_labelPainter.text = null;
}
// Changing the textDirection can result in the layout changing, because the
// bidi algorithm might line up the glyphs differently which can result in
// different ligatures, different shapes, etc. So we always markNeedsLayout.
markNeedsLayout();
}
double get _trackLength => size.width - 2.0 * _kReactionRadius;
@ -633,7 +640,6 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler {
..lineTo(center.dx + tipAttachment, center.dy + tipAttachment)
..close();
canvas.drawPath(path, primaryPaint);
_labelPainter.layout();
final Offset labelOffset = new Offset(
center.dx - _labelPainter.width / 2.0,
center.dy - _labelPainter.height / 2.0

View File

@ -69,8 +69,8 @@ class TextField extends StatefulWidget {
/// the number of lines. By default, it is 1, meaning this is a single-line
/// text field. If it is not null, it must be greater than zero.
///
/// The [keyboardType], [autofocus], [obscureText], and [autocorrect] arguments
/// must not be null.
/// The [keyboardType], [textAlign], [autofocus], [obscureText], and
/// [autocorrect] arguments must not be null.
const TextField({
Key key,
this.controller,
@ -78,7 +78,7 @@ class TextField extends StatefulWidget {
this.decoration: const InputDecoration(),
this.keyboardType: TextInputType.text,
this.style,
this.textAlign,
this.textAlign: TextAlign.start,
this.autofocus: false,
this.obscureText: false,
this.autocorrect: true,
@ -87,6 +87,7 @@ class TextField extends StatefulWidget {
this.onSubmitted,
this.inputFormatters,
}) : assert(keyboardType != null),
assert(textAlign != null),
assert(autofocus != null),
assert(obscureText != null),
assert(autocorrect != null),
@ -125,6 +126,8 @@ class TextField extends StatefulWidget {
final TextStyle style;
/// How the text being edited should be aligned horizontally.
///
/// Defaults to [TextAlign.start].
final TextAlign textAlign;
/// Whether this text field should focus itself if nothing else is already

View File

@ -356,7 +356,8 @@ List<TextPainter> _initPainters(TextTheme textTheme, List<String> labels) {
// TODO(abarth): Handle textScaleFactor.
// https://github.com/flutter/flutter/issues/5939
painters[i] = new TextPainter(
text: new TextSpan(style: style, text: label)
text: new TextSpan(style: style, text: label),
textDirection: TextDirection.ltr,
)..layout();
}
return painters;

View File

@ -247,9 +247,10 @@ class _FlutterLogoPainter extends BoxPainter {
fontFamily: 'Roboto',
fontSize: 100.0 * 350.0 / 247.0, // 247 is the height of the F when the fontSize is 350, assuming device pixel ratio 1.0
fontWeight: FontWeight.w300,
textBaseline: TextBaseline.alphabetic
)
)
textBaseline: TextBaseline.alphabetic,
),
),
textDirection: TextDirection.ltr,
);
_textPainter.layout();
final ui.TextBox textSize = _textPainter.getBoxesForSelection(const TextSelection(baseOffset: 0, extentOffset: kLabel.length)).single;

View File

@ -33,21 +33,26 @@ final String _kZeroWidthSpace = new String.fromCharCode(0x200B);
class TextPainter {
/// Creates a text painter that paints the given text.
///
/// The text argument is optional but [text] must be non-null before calling
/// [layout].
/// The `text` and `textDirection` arguments are optional but [text] and
/// [textDirection] must be non-null before calling [layout].
///
/// The [textAlign] property must not be null.
///
/// The [maxLines] property, if non-null, must be greater than zero.
TextPainter({
TextSpan text,
TextAlign textAlign,
TextAlign textAlign: TextAlign.start,
TextDirection textDirection,
double textScaleFactor: 1.0,
int maxLines,
String ellipsis,
}) : assert(text == null || text.debugAssertIsValid()),
assert(textAlign != null),
assert(textScaleFactor != null),
assert(maxLines == null || maxLines > 0),
_text = text,
_textAlign = textAlign,
_textDirection = textDirection,
_textScaleFactor = textScaleFactor,
_maxLines = maxLines,
_ellipsis = ellipsis;
@ -58,6 +63,8 @@ class TextPainter {
/// The (potentially styled) text to paint.
///
/// After this is set, you must call [layout] before the next call to [paint].
///
/// This and [textDirection] must be non-null before you call [layout].
TextSpan get text => _text;
TextSpan _text;
set text(TextSpan value) {
@ -74,9 +81,12 @@ class TextPainter {
/// How the text should be aligned horizontally.
///
/// After this is set, you must call [layout] before the next call to [paint].
///
/// The [textAlign] property must not be null. It defaults to [TextAlign.start].
TextAlign get textAlign => _textAlign;
TextAlign _textAlign;
set textAlign(TextAlign value) {
assert(value != null);
if (_textAlign == value)
return;
_textAlign = value;
@ -84,6 +94,32 @@ class TextPainter {
_needsLayout = true;
}
/// The default directionality of the text.
///
/// This controls how the [TextAlign.start], [TextAlign.end], and
/// [TextAlign.justify] values of [textAlign] are resolved.
///
/// This is also used to disambiguate how to render bidirectional text. For
/// example, if the [text] 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 Hebrow phrase on
/// its left.
///
/// After this is set, you must call [layout] before the next call to [paint].
///
/// This and [text] must be non-null before you call [layout].
TextDirection get textDirection => _textDirection;
TextDirection _textDirection;
set textDirection(TextDirection value) {
if (_textDirection == value)
return;
_textDirection = value;
_paragraph = null;
_layoutTemplate = null; // Shouldn't really matter, but for strict correctness...
_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
@ -149,14 +185,20 @@ class TextPainter {
ui.Paragraph _layoutTemplate;
ui.ParagraphStyle _createParagraphStyle() {
ui.ParagraphStyle _createParagraphStyle([TextDirection defaultTextDirection]) {
// The defaultTextDirection argument is used for preferredLineHeight in case
// textDirection hasn't yet been set.
assert(textAlign != null);
assert(textDirection != null || defaultTextDirection != null, 'TextPainter.textDirection must be set to a non-null value before using the TextPainter.');
return _text.style?.getParagraphStyle(
textAlign: textAlign,
textDirection: textDirection ?? defaultTextDirection,
textScaleFactor: textScaleFactor,
maxLines: _maxLines,
ellipsis: _ellipsis,
) ?? new ui.ParagraphStyle(
textAlign: textAlign,
textDirection: textDirection ?? defaultTextDirection,
maxLines: maxLines,
ellipsis: ellipsis,
);
@ -169,11 +211,17 @@ class TextPainter {
/// relative a typical line of text.
///
/// Obtaining this value does not require calling [layout].
///
/// The style of the [text] property is used to determine the font settings
/// that contribute to the [preferredLineHeight]. If [text] is null or if it
/// specifies no styles, the default [TextStyle] values are used (a 10 pixel
/// sans-serif font).
double get preferredLineHeight {
assert(text != null);
if (_layoutTemplate == null) {
final ui.ParagraphBuilder builder = new ui.ParagraphBuilder(_createParagraphStyle());
if (text.style != null)
final ui.ParagraphBuilder builder = new ui.ParagraphBuilder(
_createParagraphStyle(TextDirection.rtl),
); // direction doesn't matter, text is just a zero width space
if (text?.style != null)
builder.pushStyle(text.style.getTextStyle(textScaleFactor: textScaleFactor));
builder.addText(_kZeroWidthSpace);
_layoutTemplate = builder.build()
@ -272,10 +320,14 @@ class TextPainter {
/// Computes the visual position of the glyphs for painting the text.
///
/// The text will layout with a width that's as close to its max intrinsic
/// width as possible while still being greater than or equal to minWidth and
/// less than or equal to maxWidth.
/// width as possible while still being greater than or equal to `minWidth` and
/// less than or equal to `maxWidth`.
///
/// The [text] and [textDirection] properties must be non-null before this is
/// called.
void layout({ double minWidth: 0.0, double maxWidth: double.INFINITY }) {
assert(_text != null);
assert(text != null, 'TextPainter.text must be set to a non-null value before using the TextPainter.');
assert(textDirection != null, 'TextPainter.textDirection must be set to a non-null value before using the TextPainter.');
if (!_needsLayout && minWidth == _lastMinWidth && maxWidth == _lastMaxWidth)
return;
_needsLayout = false;
@ -352,15 +404,34 @@ class TextPainter {
}
Offset get _emptyOffset {
// TODO(abarth): Handle the directionality of the text painter itself.
switch (textAlign ?? TextAlign.left) {
assert(!_needsLayout); // implies textDirection is non-null
assert(textAlign != null);
switch (textAlign) {
case TextAlign.left:
case TextAlign.justify:
return Offset.zero;
case TextAlign.right:
return new Offset(width, 0.0);
case TextAlign.center:
return new Offset(width / 2.0, 0.0);
case TextAlign.justify:
case TextAlign.start:
assert(textDirection != null);
switch (textDirection) {
case TextDirection.rtl:
return new Offset(width, 0.0);
case TextDirection.ltr:
return Offset.zero;
}
return null;
case TextAlign.end:
assert(textDirection != null);
switch (textDirection) {
case TextDirection.rtl:
return Offset.zero;
case TextDirection.ltr:
return new Offset(width, 0.0);
}
return null;
}
return null;
}

View File

@ -369,6 +369,7 @@ class TextStyle extends Diagnosticable {
/// specified and non-null, must be greater than zero.
ui.ParagraphStyle getParagraphStyle({
TextAlign textAlign,
TextDirection textDirection,
double textScaleFactor: 1.0,
String ellipsis,
int maxLines,
@ -377,6 +378,7 @@ class TextStyle extends Diagnosticable {
assert(maxLines == null || maxLines > 0);
return new ui.ParagraphStyle(
textAlign: textAlign,
textDirection: textDirection,
fontWeight: fontWeight,
fontStyle: fontStyle,
fontFamily: fontFamily,
@ -418,7 +420,7 @@ class TextStyle extends Diagnosticable {
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other is! TextStyle)
if (other.runtimeType != runtimeType)
return false;
final TextStyle typedOther = other;
return inherit == typedOther.inherit &&

View File

@ -87,6 +87,10 @@ class TextSelectionPoint {
class RenderEditable extends RenderBox {
/// Creates a render object that implements the visual aspects of a text field.
///
/// The [textAlign] argument must not be null. It defaults to [TextAlign.start].
///
/// The [textDirection] argument must not be null.
///
/// If [showCursor] is not specified, then it defaults to hiding the cursor.
///
/// The [maxLines] property can be set to null to remove the restriction on
@ -97,7 +101,8 @@ class RenderEditable extends RenderBox {
/// ViewportOffset.zero] if you have no need for scrolling.
RenderEditable({
TextSpan text,
TextAlign textAlign,
@required TextDirection textDirection,
TextAlign textAlign: TextAlign.start,
Color cursorColor,
ValueNotifier<bool> showCursor,
int maxLines: 1,
@ -107,10 +112,17 @@ class RenderEditable extends RenderBox {
@required ViewportOffset offset,
this.onSelectionChanged,
this.onCaretChanged,
}) : assert(maxLines == null || maxLines > 0),
}) : assert(textAlign != null),
assert(textDirection != null, 'RenderEditable created without a textDirection.'),
assert(maxLines == null || maxLines > 0),
assert(textScaleFactor != null),
assert(offset != null),
_textPainter = new TextPainter(text: text, textAlign: textAlign, textScaleFactor: textScaleFactor),
_textPainter = new TextPainter(
text: text,
textAlign: textAlign,
textDirection: textDirection,
textScaleFactor: textScaleFactor,
),
_cursorColor = cursorColor,
_showCursor = showCursor ?? new ValueNotifier<bool>(false),
_maxLines = maxLines,
@ -146,7 +158,7 @@ class RenderEditable extends RenderBox {
markNeedsLayout();
}
/// The text to display
/// The text to display.
TextSpan get text => _textPainter.text;
final TextPainter _textPainter;
set text(TextSpan value) {
@ -157,14 +169,39 @@ class RenderEditable extends RenderBox {
}
/// How the text should be aligned horizontally.
///
/// This must not be null.
TextAlign get textAlign => _textPainter.textAlign;
set textAlign(TextAlign value) {
assert(value != null);
if (_textPainter.textAlign == value)
return;
_textPainter.textAlign = value;
markNeedsPaint();
}
/// The directionality of the text.
///
/// This decides how the [TextAlign.start], [TextAlign.end], and
/// [TextAlign.justify] values of [textAlign] are interpreted.
///
/// This is also used to disambiguate how to render bidirectional text. For
/// example, if the [text] 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 Hebrow phrase on
/// its left.
///
/// This must not be null.
TextDirection get textDirection => _textPainter.textDirection;
set textDirection(TextDirection value) {
assert(value != null);
if (_textPainter.textDirection == value)
return;
_textPainter.textDirection = value;
markNeedsTextLayout();
}
/// The color to use when painting the cursor.
Color get cursorColor => _cursorColor;
Color _cursorColor;

View File

@ -993,12 +993,13 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
}
context.canvas.drawRect(markerRect, _debugMarkerPaint);
_debugMarkerLabel ??= new TextPainter();
_debugMarkerLabel.text = new TextSpan( // this is a no-op if the label hasn't changed
_debugMarkerLabel ??= new TextPainter()
..textDirection = TextDirection.ltr; // This label is in English.
_debugMarkerLabel.text = new TextSpan( // This is a no-op if the label hasn't changed.
text: label,
style: _debugMarkerTextStyle,
);
_debugMarkerLabel.layout(); // this is a no-op if the label hasn't changed
_debugMarkerLabel.layout(); // This is a no-op if the label hasn't changed.
// TODO(ianh): RTL support
switch (direction) {

View File

@ -2867,11 +2867,11 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
description.add(new DiagnosticsProperty<dynamic>('creator', debugCreator, defaultValue: null));
description.add(new DiagnosticsProperty<ParentData>('parentData', parentData, tooltip: _debugCanParentUseSize == true ? "can use size" : null));
description.add(new DiagnosticsProperty<Constraints>('constraints', constraints));
description.add(new DiagnosticsProperty<ParentData>('parentData', parentData, tooltip: _debugCanParentUseSize == true ? 'can use size' : null, ifNull: 'MISSING'));
description.add(new DiagnosticsProperty<Constraints>('constraints', constraints, ifNull: 'MISSING'));
// don't access it via the "layer" getter since that's only valid when we don't need paint
description.add(new DiagnosticsProperty<OffsetLayer>('layer', _layer, defaultValue: null));
description.add(new DiagnosticsProperty<SemanticsNode>('_semantics', _semantics, defaultValue: null));
description.add(new DiagnosticsProperty<SemanticsNode>('semantics node', _semantics, defaultValue: null));
description.add(new FlagProperty(
'isBlockingSemanticsOfPreviouslyPaintedNodes',
value: isBlockingSemanticsOfPreviouslyPaintedNodes,

View File

@ -32,19 +32,22 @@ const String _kEllipsis = '\u2026';
class RenderParagraph extends RenderBox {
/// Creates a paragraph render object.
///
/// The [text], [overflow], [softWrap], and [textScaleFactor] arguments must
/// not be null.
/// The [text], [textAlign], [textDirection], [overflow], [softWrap], and
/// [textScaleFactor] arguments must not be null.
///
/// The [maxLines] property may be null (and indeed defaults to null), but if
/// it is not null, it must be greater than zero.
RenderParagraph(TextSpan text, {
TextAlign textAlign,
TextAlign textAlign: TextAlign.start,
@required TextDirection textDirection,
bool softWrap: true,
TextOverflow overflow: TextOverflow.clip,
double textScaleFactor: 1.0,
int maxLines,
}) : assert(text != null),
assert(text.debugAssertIsValid()),
assert(textAlign != null),
assert(textDirection != null),
assert(softWrap != null),
assert(overflow != null),
assert(textScaleFactor != null),
@ -54,6 +57,7 @@ class RenderParagraph extends RenderBox {
_textPainter = new TextPainter(
text: text,
textAlign: textAlign,
textDirection: textDirection,
textScaleFactor: textScaleFactor,
maxLines: maxLines,
ellipsis: overflow == TextOverflow.ellipsis ? _kEllipsis : null,
@ -84,12 +88,35 @@ class RenderParagraph extends RenderBox {
/// How the text should be aligned horizontally.
TextAlign get textAlign => _textPainter.textAlign;
set textAlign(TextAlign value) {
assert(value != null);
if (_textPainter.textAlign == value)
return;
_textPainter.textAlign = value;
markNeedsPaint();
}
/// The directionality of the text.
///
/// This decides how the [TextAlign.start], [TextAlign.end], and
/// [TextAlign.justify] values of [textAlign] are interpreted.
///
/// This is also used to disambiguate how to render bidirectional text. For
/// example, if the [text] 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 Hebrow phrase on
/// its left.
///
/// This must not be null.
TextDirection get textDirection => _textPainter.textDirection;
set textDirection(TextDirection value) {
assert(value != null);
if (_textPainter.textDirection == value)
return;
_textPainter.textDirection = value;
markNeedsLayout();
}
/// Whether the text should break at soft line breaks.
///
/// If false, the glyphs in the text will be positioned as if there was
@ -148,10 +175,8 @@ class RenderParagraph extends RenderBox {
}
void _layoutText({ double minWidth: 0.0, double maxWidth: double.INFINITY }) {
_textPainter.layout(
minWidth: minWidth,
maxWidth: _softWrap || _overflow == TextOverflow.ellipsis ? maxWidth : double.INFINITY
);
final bool widthMatters = softWrap || overflow == TextOverflow.ellipsis;
_textPainter.layout(minWidth: minWidth, maxWidth: widthMatters ? maxWidth : double.INFINITY);
}
void _layoutTextWithConstraints(BoxConstraints constraints) {
@ -245,14 +270,24 @@ class RenderParagraph extends RenderBox {
_overflowShader = null;
break;
case TextOverflow.fade:
assert(textDirection != null);
final TextPainter fadeSizePainter = new TextPainter(
text: new TextSpan(style: _textPainter.text.style, text: '\u2026'),
textScaleFactor: textScaleFactor
textDirection: textDirection,
textScaleFactor: textScaleFactor,
)..layout();
if (didOverflowWidth) {
final double fadeEnd = size.width;
final double fadeStart = fadeEnd - fadeSizePainter.width;
// TODO(abarth): This shader has an LTR bias.
double fadeEnd, fadeStart;
switch (textDirection) {
case TextDirection.rtl:
fadeEnd = 0.0;
fadeStart = fadeSizePainter.width;
break;
case TextDirection.ltr:
fadeEnd = size.width;
fadeStart = fadeEnd - fadeSizePainter.width;
break;
}
_overflowShader = new ui.Gradient.linear(
new Offset(fadeStart, 0.0),
new Offset(fadeEnd, 0.0),
@ -373,10 +408,22 @@ class RenderParagraph extends RenderBox {
void _annotate(SemanticsNode node) {
node.label = text.toPlainText();
node.textDirection = textDirection;
}
@override
List<DiagnosticsNode> debugDescribeChildren() {
return <DiagnosticsNode>[text.toDiagnosticsNode(name: 'text', style: DiagnosticsTreeStyle.transition)];
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
description.add(new EnumProperty<TextAlign>('textAlign', textAlign));
description.add(new EnumProperty<TextDirection>('textDirection', textDirection));
description.add(new FlagProperty('softWrap', value: softWrap, ifTrue: 'wrapping at box width', ifFalse: 'no wrapping except at line break characters', showName: true));
description.add(new EnumProperty<TextOverflow>('overflow', overflow));
description.add(new DoubleProperty('textScaleFactor', textScaleFactor, defaultValue: 1.0));
description.add(new IntProperty('maxLines', maxLines, ifNull: 'unlimited'));
}
}

View File

@ -3114,17 +3114,21 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
/// Creates a render object that attaches a semantic annotation.
///
/// The [container] argument must not be null.
///
/// If the [label] is not null, the [textDirection] must also not be null.
RenderSemanticsAnnotations({
RenderBox child,
bool container: false,
bool checked,
bool selected,
String label,
TextDirection textDirection,
}) : assert(container != null),
_container = container,
_checked = checked,
_selected = selected,
_label = label,
_textDirection = textDirection,
super(child);
/// If 'container' is true, this RenderObject will introduce a new
@ -3182,11 +3186,24 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
markNeedsSemanticsUpdate(onlyLocalUpdates: (value != null) == hadValue);
}
/// If non-null, sets the [SemanticsNode.textDirection] semantic to the given value.
///
/// This must not be null if [label] is not null.
TextDirection get textDirection => _textDirection;
TextDirection _textDirection;
set textDirection(TextDirection value) {
if (textDirection == value)
return;
final bool hadValue = textDirection != null;
_textDirection = value;
markNeedsSemanticsUpdate(onlyLocalUpdates: (value != null) == hadValue);
}
@override
bool get isSemanticBoundary => container;
@override
SemanticsAnnotator get semanticsAnnotator => checked != null || selected != null || label != null ? _annotate : null;
SemanticsAnnotator get semanticsAnnotator => checked != null || selected != null || label != null || textDirection != null ? _annotate : null;
void _annotate(SemanticsNode node) {
if (checked != null) {
@ -3198,6 +3215,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
node.isSelected = selected;
if (label != null)
node.label = label;
if (textDirection != null)
node.textDirection = textDirection;
}
}

View File

@ -92,16 +92,20 @@ class SemanticsData {
/// Creates a semantics data object.
///
/// The [flags], [actions], [label], and [Rect] arguments must not be null.
///
/// If [label] is not empty, then [textDirection] must also not be null.
const SemanticsData({
@required this.flags,
@required this.actions,
@required this.label,
@required this.textDirection,
@required this.rect,
@required this.tags,
this.transform
this.transform,
}) : assert(flags != null),
assert(actions != null),
assert(label != null),
assert(label == '' || textDirection != null, 'A SemanticsData object with label "$label" had a null textDirection.'),
assert(rect != null),
assert(tags != null);
@ -112,8 +116,13 @@ class SemanticsData {
final int actions;
/// A textual description of this node.
///
/// The text's reading direction is given by [textDirection].
final String label;
/// The reading direction for the text in [label].
final TextDirection textDirection;
/// The bounding box for this node in its coordinate system.
final Rect rect;
@ -149,6 +158,8 @@ class SemanticsData {
}
if (label.isNotEmpty)
buffer.write('; "$label"');
if (textDirection != null)
buffer.write('; $textDirection');
buffer.write(')');
return buffer.toString();
}
@ -161,13 +172,14 @@ class SemanticsData {
return typedOther.flags == flags
&& typedOther.actions == actions
&& typedOther.label == label
&& typedOther.textDirection == textDirection
&& typedOther.rect == rect
&& setEquals(typedOther.tags, tags)
&& typedOther.transform == transform;
}
@override
int get hashCode => hashValues(flags, actions, label, rect, tags, transform);
int get hashCode => hashValues(flags, actions, label, textDirection, rect, tags, transform);
}
/// A node that represents some semantic data.
@ -342,6 +354,8 @@ class SemanticsNode extends AbstractNode {
set isSelected(bool value) => _setFlag(SemanticsFlags.isSelected, value);
/// A textual description of this node.
///
/// The text's reading direction is given by [textDirection].
String get label => _label;
String _label = '';
set label(String value) {
@ -352,6 +366,17 @@ class SemanticsNode extends AbstractNode {
}
}
/// The reading direction for the text in [label].
TextDirection get textDirection => _textDirection;
TextDirection _textDirection;
set textDirection(TextDirection value) {
assert(value != null);
if (_textDirection != value) {
_textDirection = value;
_markDirty();
}
}
final Set<SemanticsTag> _tags = new Set<SemanticsTag>();
/// Tags the [SemanticsNode] with [tag].
@ -385,6 +410,7 @@ class SemanticsNode extends AbstractNode {
if (hadInheritedMergeAllDescendantsIntoThisNode)
_inheritedMergeAllDescendantsIntoThisNodeValue = true;
_label = '';
_textDirection = null;
_tags.clear();
_markDirty();
}
@ -598,18 +624,31 @@ class SemanticsNode extends AbstractNode {
int flags = _flags;
int actions = _actions;
String label = _label;
TextDirection textDirection = _textDirection;
final Set<SemanticsTag> tags = new Set<SemanticsTag>.from(_tags);
if (mergeAllDescendantsIntoThisNode) {
_visitDescendants((SemanticsNode node) {
flags |= node._flags;
actions |= node._actions;
textDirection ??= node._textDirection;
tags.addAll(node._tags);
if (node.label.isNotEmpty) {
String nestedLabel = node.label;
if (textDirection != node.textDirection && node.textDirection != null) {
switch (node.textDirection) {
case TextDirection.rtl:
nestedLabel = '${Unicode.RLE}$nestedLabel${Unicode.PDF}';
break;
case TextDirection.ltr:
nestedLabel = '${Unicode.LRE}$nestedLabel${Unicode.PDF}';
break;
}
}
if (label.isEmpty)
label = node.label;
label = nestedLabel;
else
label = '$label\n${node.label}';
label = '$label\n$nestedLabel';
}
return true;
});
@ -619,6 +658,7 @@ class SemanticsNode extends AbstractNode {
flags: flags,
actions: actions,
label: label,
textDirection: textDirection,
rect: rect,
transform: transform,
tags: tags,
@ -650,6 +690,7 @@ class SemanticsNode extends AbstractNode {
actions: data.actions,
rect: data.rect,
label: data.label,
textDirection: data.textDirection,
transform: data.transform?.storage ?? _kIdentityTransform,
children: children,
);
@ -696,6 +737,8 @@ class SemanticsNode extends AbstractNode {
buffer.write('; selected');
if (label.isNotEmpty)
buffer.write('; "$label"');
if (textDirection != null)
buffer.write('; $textDirection');
buffer.write(')');
return buffer.toString();
}

View File

@ -553,7 +553,8 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor {
for (int index = indexOf(firstChild) - 1; index >= firstIndex; --index) {
final SliverGridGeometry gridGeometry = layout.getGeometryForChildIndex(index);
final RenderBox child = insertAndLayoutLeadingChild(
gridGeometry.getBoxConstraints(constraints));
gridGeometry.getBoxConstraints(constraints),
);
final SliverGridParentData childParentData = child.parentData;
childParentData.layoutOffset = gridGeometry.scrollOffset;
childParentData.crossAxisOffset = gridGeometry.crossAxisOffset;

View File

@ -41,13 +41,15 @@ enum BannerLocation {
class BannerPainter extends CustomPainter {
/// Creates a banner painter.
///
/// The [message] and [location] arguments must not be null.
/// The [message], [textDirection], and [location] arguments must not be null.
BannerPainter({
@required this.message,
@required this.textDirection,
@required this.location,
this.color: _kColor,
this.textStyle: _kTextStyle,
}) : assert(message != null),
assert(textDirection != null),
assert(location != null),
assert(color != null),
assert(textStyle != null);
@ -55,6 +57,16 @@ class BannerPainter extends CustomPainter {
/// The message to show in the banner.
final String message;
/// The directionality of the text.
///
/// This is used to disambiguate how to render bidirectional text. For
/// example, if the message 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 Hebrow phrase on
/// its left.
final TextDirection textDirection;
/// Where to show the banner (e.g., the upper right corder).
final BannerLocation location;
@ -82,6 +94,7 @@ class BannerPainter extends CustomPainter {
_textPainter = new TextPainter(
text: new TextSpan(style: textStyle, text: message),
textAlign: TextAlign.center,
textDirection: textDirection,
);
_prepared = true;
}
@ -169,6 +182,7 @@ class Banner extends StatelessWidget {
Key key,
this.child,
@required this.message,
this.textDirection,
@required this.location,
this.color: _kColor,
this.textStyle: _kTextStyle,
@ -184,6 +198,18 @@ class Banner extends StatelessWidget {
/// The message to show in the banner.
final String message;
/// The directionality of the text.
///
/// This is used to disambiguate how to render bidirectional text. For
/// example, if the message 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 Hebrow phrase on
/// its left.
///
/// Defaults to the ambient [Directionality], if any.
final TextDirection textDirection;
/// Where to show the banner (e.g., the upper right corder).
final BannerLocation location;
@ -198,6 +224,7 @@ class Banner extends StatelessWidget {
return new CustomPaint(
foregroundPainter: new BannerPainter(
message: message,
textDirection: textDirection ?? Directionality.of(context),
location: location,
color: color,
textStyle: textStyle,
@ -210,6 +237,7 @@ class Banner extends StatelessWidget {
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
description.add(new StringProperty('message', message, showName: false));
description.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
description.add(new EnumProperty<BannerLocation>('location', location));
description.add(new DiagnosticsProperty<Color>('color', color, showName: false));
textStyle?.debugFillProperties(description, prefix: 'text ');
@ -236,7 +264,9 @@ class CheckedModeBanner extends StatelessWidget {
result = new Banner(
child: result,
message: 'SLOW MODE',
location: BannerLocation.topRight);
textDirection: TextDirection.ltr,
location: BannerLocation.topRight,
);
return true;
});
return result;

View File

@ -3704,20 +3704,25 @@ class Flow extends MultiChildRenderObjectWidget {
class RichText extends LeafRenderObjectWidget {
/// Creates a paragraph of rich text.
///
/// The [text], [softWrap], [overflow], nad [textScaleFactor] arguments must
/// not be null.
/// The [text], [textAlign], [softWrap], [overflow], nad [textScaleFactor]
/// arguments must not be null.
///
/// The [maxLines] property may be null (and indeed defaults to null), but if
/// it is not null, it must be greater than zero.
///
/// The [textDirection], if null, defaults to the ambient [Directionality],
/// which in that case must not be null.
const RichText({
Key key,
@required this.text,
this.textAlign,
this.textAlign: TextAlign.start,
this.textDirection,
this.softWrap: true,
this.overflow: TextOverflow.clip,
this.textScaleFactor: 1.0,
this.maxLines,
}) : assert(text != null),
assert(textAlign != null),
assert(softWrap != null),
assert(overflow != null),
assert(textScaleFactor != null),
@ -3730,6 +3735,22 @@ class RichText extends LeafRenderObjectWidget {
/// 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 [text] 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 Hebrow phrase on
/// its left.
///
/// Defaults to the ambient [Directionality], if any. If there is no ambient
/// [Directionality], then this must not be null.
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.
@ -3754,8 +3775,11 @@ class RichText extends LeafRenderObjectWidget {
@override
RenderParagraph createRenderObject(BuildContext context) {
final TextDirection direction = textDirection ?? Directionality.of(context);
assert(direction != null, 'A RichText was created with no textDirection and no ambient Directionality widget.');
return new RenderParagraph(text,
textAlign: textAlign,
textDirection: direction,
softWrap: softWrap,
overflow: overflow,
textScaleFactor: textScaleFactor,
@ -3768,6 +3792,7 @@ class RichText extends LeafRenderObjectWidget {
renderObject
..text = text
..textAlign = textAlign
..textDirection = textDirection ?? Directionality.of(context)
..softWrap = softWrap
..overflow = overflow
..textScaleFactor = textScaleFactor
@ -4289,6 +4314,7 @@ class Semantics extends SingleChildRenderObjectWidget {
this.checked,
this.selected,
this.label,
this.textDirection,
}) : assert(container != null),
super(key: key, child: child);
@ -4317,8 +4343,20 @@ class Semantics extends SingleChildRenderObjectWidget {
final bool selected;
/// Provides a textual description of the widget.
///
/// If a label is provided, there must either by an ambient [Directionality]
/// or an explicit [textDirection] should be provided.
final String label;
/// The reading direction of the [label].
///
/// Defaults to the ambient [Directionality].
final TextDirection textDirection;
TextDirection _getTextDirection(BuildContext context) {
return textDirection ?? (label != null ? Directionality.of(context) : null);
}
@override
RenderSemanticsAnnotations createRenderObject(BuildContext context) {
return new RenderSemanticsAnnotations(
@ -4326,6 +4364,7 @@ class Semantics extends SingleChildRenderObjectWidget {
checked: checked,
selected: selected,
label: label,
textDirection: _getTextDirection(context),
);
}
@ -4335,7 +4374,8 @@ class Semantics extends SingleChildRenderObjectWidget {
..container = container
..checked = checked
..selected = selected
..label = label;
..label = label
..textDirection = _getTextDirection(context);
}
@override
@ -4344,7 +4384,8 @@ class Semantics extends SingleChildRenderObjectWidget {
description.add(new DiagnosticsProperty<bool>('container', container));
description.add(new DiagnosticsProperty<bool>('checked', checked, defaultValue: null));
description.add(new DiagnosticsProperty<bool>('selected', selected, defaultValue: null));
description.add(new StringProperty('label', label));
description.add(new StringProperty('label', label, defaultValue: ''));
description.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
}
}

View File

@ -144,8 +144,8 @@ class EditableText extends StatefulWidget {
/// the number of lines. By default, it is 1, meaning this is a single-line
/// text field. If it is not null, it must be greater than zero.
///
/// The [controller], [focusNode], [style], and [cursorColor] arguments must
/// not be null.
/// The [controller], [focusNode], [style], [cursorColor], and [textAlign]
/// arguments must not be null.
EditableText({
Key key,
@required this.controller,
@ -154,7 +154,8 @@ class EditableText extends StatefulWidget {
this.autocorrect: true,
@required this.style,
@required this.cursorColor,
this.textAlign,
this.textAlign: TextAlign.start,
this.textDirection,
this.textScaleFactor,
this.maxLines: 1,
this.autofocus: false,
@ -171,6 +172,7 @@ class EditableText extends StatefulWidget {
assert(autocorrect != null),
assert(style != null),
assert(cursorColor != null),
assert(textAlign != null),
assert(maxLines == null || maxLines > 0),
assert(autofocus != null),
inputFormatters = maxLines == 1
@ -201,8 +203,25 @@ class EditableText extends StatefulWidget {
final TextStyle style;
/// How the text should be aligned horizontally.
///
/// Defaults to [TextAlign.start].
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 text 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 Hebrow phrase on
/// its left.
///
/// Defaults to the ambient [Directionality], if any.
final TextDirection textDirection;
/// 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
@ -266,6 +285,7 @@ class EditableText extends StatefulWidget {
description.add(new DiagnosticsProperty<bool>('autocorrect', autocorrect, defaultValue: true));
style?.debugFillProperties(description);
description.add(new EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: null));
description.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
description.add(new DoubleProperty('textScaleFactor', textScaleFactor, defaultValue: null));
description.add(new IntProperty('maxLines', maxLines, defaultValue: 1));
description.add(new DiagnosticsProperty<bool>('autofocus', autofocus, defaultValue: false));
@ -575,6 +595,12 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
updateKeepAlive();
}
TextDirection get _textDirection {
final TextDirection result = widget.textDirection ?? Directionality.of(context);
assert(result != null, '$runtimeType created without a textDirection and with no ambient Directionality.');
return result;
}
@override
Widget build(BuildContext context) {
FocusScope.of(context).reparentIfNeeded(widget.focusNode);
@ -595,6 +621,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
selectionColor: widget.selectionColor,
textScaleFactor: widget.textScaleFactor ?? MediaQuery.of(context, nullOk: true)?.textScaleFactor ?? 1.0,
textAlign: widget.textAlign,
textDirection: _textDirection,
obscureText: widget.obscureText,
obscureShowCharacterAtIndex: _obscureShowCharTicksPending > 0 ? _obscureLatestCharIndex : null,
autocorrect: widget.autocorrect,
@ -619,13 +646,15 @@ class _Editable extends LeafRenderObjectWidget {
this.selectionColor,
this.textScaleFactor,
this.textAlign,
@required this.textDirection,
this.obscureText,
this.obscureShowCharacterAtIndex,
this.autocorrect,
this.offset,
this.onSelectionChanged,
this.onCaretChanged,
}) : super(key: key);
}) : assert(textDirection != null),
super(key: key);
final TextEditingValue value;
final TextStyle style;
@ -635,6 +664,7 @@ class _Editable extends LeafRenderObjectWidget {
final Color selectionColor;
final double textScaleFactor;
final TextAlign textAlign;
final TextDirection textDirection;
final bool obscureText;
final int obscureShowCharacterAtIndex;
final bool autocorrect;
@ -652,6 +682,7 @@ class _Editable extends LeafRenderObjectWidget {
selectionColor: selectionColor,
textScaleFactor: textScaleFactor,
textAlign: textAlign,
textDirection: textDirection,
selection: value.selection,
offset: offset,
onSelectionChanged: onSelectionChanged,
@ -669,6 +700,7 @@ class _Editable extends LeafRenderObjectWidget {
..selectionColor = selectionColor
..textScaleFactor = textScaleFactor
..textAlign = textAlign
..textDirection = textDirection
..selection = value.selection
..offset = offset
..onSelectionChanged = onSelectionChanged

View File

@ -17,6 +17,10 @@ import 'icon_theme_data.dart';
/// Icons are not interactive. For an interactive icon, consider material's
/// [IconButton].
///
/// There must be an ambient [Directionality] widget when using [Icon].
/// Typically this is introduced automatically by the [WidgetsApp] or
/// [MaterialApp].
///
/// See also:
///
/// * [IconButton], for interactive icons.
@ -80,6 +84,9 @@ class Icon extends StatelessWidget {
@override
Widget build(BuildContext context) {
final TextDirection textDirection = Directionality.of(context);
assert(textDirection != null, 'Icon widgets required an ambient Directionality.');
final IconThemeData iconTheme = IconTheme.of(context);
final double iconSize = size ?? iconTheme.size;
@ -98,18 +105,19 @@ class Icon extends StatelessWidget {
height: iconSize,
child: new Center(
child: new RichText(
textDirection: textDirection, // Since we already fetched it for the assert...
text: new TextSpan(
text: new String.fromCharCode(icon.codePoint),
style: new TextStyle(
inherit: false,
color: iconColor,
fontSize: iconSize,
fontFamily: icon.fontFamily
)
)
)
)
)
fontFamily: icon.fontFamily,
),
),
),
),
),
);
}

View File

@ -228,15 +228,29 @@ String _getMessage(SemanticsNode node) {
if (isAdjustable)
annotations.add('adjustable');
String message;
if (annotations.isEmpty) {
assert(data.label != null);
message = data.label;
} else {
String message;
if (data.label.isEmpty) {
message = annotations.join('; ');
} else {
message = '${data.label} (${annotations.join('; ')})';
String label;
if (data.textDirection == null) {
label = '${Unicode.FSI}${data.label}${Unicode.PDI}';
annotations.insert(0, 'MISSING TEXT DIRECTION');
} else {
switch (data.textDirection) {
case TextDirection.rtl:
label = '${Unicode.RLI}${data.label}${Unicode.PDF}';
break;
case TextDirection.ltr:
label = data.label;
break;
}
}
if (annotations.isEmpty) {
message = label;
} else {
message = '$label (${annotations.join('; ')})';
}
}
@ -257,7 +271,11 @@ void _paintMessage(Canvas canvas, SemanticsNode node) {
canvas.save();
canvas.clipRect(rect);
final TextPainter textPainter = new TextPainter()
..text = new TextSpan(style: _messageStyle, text: message)
..text = new TextSpan(
style: _messageStyle,
text: message,
)
..textDirection = TextDirection.ltr // _getMessage always returns LTR text, even if node.label is RTL
..textAlign = TextAlign.center
..layout(maxWidth: rect.width);

View File

@ -200,6 +200,7 @@ class Text extends StatelessWidget {
Key key,
this.style,
this.textAlign,
this.textDirection,
this.softWrap,
this.overflow,
this.textScaleFactor,
@ -220,6 +221,21 @@ class Text extends StatelessWidget {
/// 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 Hebrow 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.
@ -257,7 +273,8 @@ class Text extends StatelessWidget {
if (style == null || style.inherit)
effectiveTextStyle = defaultTextStyle.style.merge(style);
return new RichText(
textAlign: textAlign ?? defaultTextStyle.textAlign,
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,

View File

@ -361,12 +361,14 @@ class _InspectorOverlayRenderState {
@required this.selected,
@required this.candidates,
@required this.tooltip,
@required this.textDirection,
});
final Rect overlayRect;
final _TransformedRect selected;
final List<_TransformedRect> candidates;
final String tooltip;
final TextDirection textDirection;
@override
bool operator ==(dynamic other) {
@ -447,6 +449,7 @@ class _InspectorOverlayLayer extends Layer {
overlayRect: overlayRect,
selected: new _TransformedRect(selected),
tooltip: selected.toString(),
textDirection: TextDirection.ltr,
candidates: candidates,
);
@ -497,15 +500,22 @@ class _InspectorOverlayLayer extends Layer {
final double offsetFromWidget = 9.0;
final double verticalOffset = (targetRect.height) / 2 + offsetFromWidget;
_paintDescription(canvas, state.tooltip, target, verticalOffset, size, targetRect);
_paintDescription(canvas, state.tooltip, state.textDirection, target, verticalOffset, size, targetRect);
// TODO(jacobr): provide an option to perform a debug paint of just the
// selected widget.
return recorder.endRecording();
}
void _paintDescription(Canvas canvas, String message, Offset target,
double verticalOffset, Size size, Rect targetRect) {
void _paintDescription(
Canvas canvas,
String message,
TextDirection textDirection,
Offset target,
double verticalOffset,
Size size,
Rect targetRect,
) {
canvas.save();
final double maxWidth = size.width - 2 * (_kScreenEdgeMargin + _kTooltipPadding);
if (_textPainter == null || _textPainter.text.text != message || _textPainterMaxWidth != maxWidth) {
@ -514,6 +524,7 @@ class _InspectorOverlayLayer extends Layer {
..maxLines = _kMaxTooltipLines
..ellipsis = '...'
..text = new TextSpan(style: _messageStyle, text: message)
..textDirection = textDirection
..layout(maxWidth: maxWidth);
}

View File

@ -14,7 +14,7 @@ const TextStyle testStyle = const TextStyle(
void main() {
testWidgets('Default layout minimum size', (WidgetTester tester) async {
await tester.pumpWidget(
const Center(child: const CupertinoButton(
boilerplate(child: const CupertinoButton(
child: const Text('X', style: testStyle),
onPressed: null,
))
@ -30,7 +30,7 @@ void main() {
testWidgets('Minimum size parameter', (WidgetTester tester) async {
final double minSize = 60.0;
await tester.pumpWidget(
new Center(child: new CupertinoButton(
boilerplate(child: new CupertinoButton(
child: const Text('X', style: testStyle),
onPressed: null,
minSize: minSize,
@ -46,7 +46,7 @@ void main() {
testWidgets('Size grows with text', (WidgetTester tester) async {
await tester.pumpWidget(
const Center(child: const CupertinoButton(
boilerplate(child: const CupertinoButton(
child: const Text('XXXX', style: testStyle),
onPressed: null,
))
@ -60,7 +60,7 @@ void main() {
});
testWidgets('Button with background is wider', (WidgetTester tester) async {
await tester.pumpWidget(const Center(child: const CupertinoButton(
await tester.pumpWidget(boilerplate(child: const CupertinoButton(
child: const Text('X', style: testStyle),
onPressed: null,
color: const Color(0xFFFFFFFF),
@ -74,7 +74,7 @@ void main() {
});
testWidgets('Custom padding', (WidgetTester tester) async {
await tester.pumpWidget(const Center(child: const CupertinoButton(
await tester.pumpWidget(boilerplate(child: const CupertinoButton(
child: const Text(' ', style: testStyle),
onPressed: null,
padding: const EdgeInsets.all(100.0),
@ -91,7 +91,7 @@ void main() {
await tester.pumpWidget(
new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return new Center(
return boilerplate(
child: new CupertinoButton(
child: const Text('Tap me'),
onPressed: () {
@ -115,7 +115,7 @@ void main() {
});
testWidgets('Disabled button doesn\'t animate', (WidgetTester tester) async {
await tester.pumpWidget(const Center(child: const CupertinoButton(
await tester.pumpWidget(boilerplate(child: const CupertinoButton(
child: const Text('Tap me'),
onPressed: null,
)));
@ -126,7 +126,7 @@ void main() {
});
testWidgets('pressedOpacity defaults to 0.1', (WidgetTester tester) async {
await tester.pumpWidget(new Center(child: new CupertinoButton(
await tester.pumpWidget(boilerplate(child: new CupertinoButton(
child: const Text('Tap me'),
onPressed: () { },
)));
@ -146,7 +146,7 @@ void main() {
testWidgets('pressedOpacity parameter', (WidgetTester tester) async {
final double pressedOpacity = 0.5;
await tester.pumpWidget(new Center(child: new CupertinoButton(
await tester.pumpWidget(boilerplate(child: new CupertinoButton(
pressedOpacity: pressedOpacity,
child: const Text('Tap me'),
onPressed: () { },
@ -165,3 +165,10 @@ void main() {
expect(opacity.opacity, pressedOpacity);
});
}
Widget boilerplate({ Widget child }) {
return new Directionality(
textDirection: TextDirection.ltr,
child: new Center(child: child),
);
}

View File

@ -64,10 +64,10 @@ void main() {
});
testWidgets('Dialog destructive action styles', (WidgetTester tester) async {
await tester.pumpWidget(const CupertinoDialogAction(
await tester.pumpWidget(boilerplate(const CupertinoDialogAction(
isDestructiveAction: true,
child: const Text('Ok'),
));
)));
final DefaultTextStyle widget = tester.widget(find.byType(DefaultTextStyle));
@ -76,10 +76,10 @@ void main() {
});
testWidgets('Dialog default action styles', (WidgetTester tester) async {
await tester.pumpWidget(const CupertinoDialogAction(
await tester.pumpWidget(boilerplate(const CupertinoDialogAction(
isDefaultAction: true,
child: const Text('Ok'),
));
)));
final DefaultTextStyle widget = tester.widget(find.byType(DefaultTextStyle));
@ -87,11 +87,11 @@ void main() {
});
testWidgets('Default and destructive style', (WidgetTester tester) async {
await tester.pumpWidget(const CupertinoDialogAction(
await tester.pumpWidget(boilerplate(const CupertinoDialogAction(
isDefaultAction: true,
isDestructiveAction: true,
child: const Text('Ok'),
));
)));
final DefaultTextStyle widget = tester.widget(find.byType(DefaultTextStyle));
@ -99,3 +99,10 @@ void main() {
expect(widget.style.color.red, greaterThan(widget.style.color.blue));
});
}
Widget boilerplate(Widget child) {
return new Directionality(
textDirection: TextDirection.ltr,
child: child,
);
}

View File

@ -14,14 +14,17 @@ void main() {
testWidgets('Does FlatButton contribute semantics', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(
new Material(
new Directionality(
textDirection: TextDirection.ltr,
child: new Material(
child: new Center(
child: new FlatButton(
onPressed: () { },
child: const Text('ABC')
)
)
)
),
),
),
),
);
expect(semantics, hasSemantics(
@ -58,10 +61,13 @@ void main() {
);
await tester.pumpWidget(
new Theme(
new Directionality(
textDirection: TextDirection.ltr,
child: new Theme(
data: new ThemeData(),
child: buttonWidget,
),
),
);
final Offset center = tester.getCenter(find.byType(MaterialButton));
@ -88,13 +94,16 @@ void main() {
);
await tester.pumpWidget(
new Theme(
new Directionality(
textDirection: TextDirection.ltr,
child: new Theme(
data: new ThemeData(
highlightColor: themeHighlightColor1,
splashColor: themeSplashColor1,
),
child: buttonWidget,
),
),
);
expect(
@ -108,13 +117,16 @@ void main() {
final Color themeHighlightColor2 = const Color(0xFF002200);
await tester.pumpWidget(
new Theme(
new Directionality(
textDirection: TextDirection.ltr,
child: new Theme(
data: new ThemeData(
highlightColor: themeHighlightColor2,
splashColor: themeSplashColor2,
),
child: buttonWidget, // same widget, so does not get updated because of us
),
),
);
expect(

View File

@ -10,7 +10,7 @@ void main() {
testWidgets('CircleAvatar with background color', (WidgetTester tester) async {
final Color backgroundColor = Colors.blue.shade400;
await tester.pumpWidget(
new Center(
wrap(
child: new CircleAvatar(
backgroundColor: backgroundColor,
radius: 50.0,
@ -33,7 +33,7 @@ void main() {
testWidgets('CircleAvatar with foreground color', (WidgetTester tester) async {
final Color foregroundColor = Colors.red.shade100;
await tester.pumpWidget(
new Center(
wrap(
child: new CircleAvatar(
foregroundColor: foregroundColor,
child: const Text('Z'),
@ -60,9 +60,9 @@ void main() {
primaryColorBrightness: Brightness.light,
);
await tester.pumpWidget(
new Theme(
wrap(
child: new Theme(
data: theme,
child: const Center(
child: const CircleAvatar(
child: const Text('Z'),
),
@ -79,3 +79,10 @@ void main() {
expect(paragraph.text.style.color, equals(theme.primaryTextTheme.title.color));
});
}
Widget wrap({ Widget child }) {
return new Directionality(
textDirection: TextDirection.ltr,
child: new Center(child: child),
);
}

View File

@ -10,15 +10,13 @@ void main() {
bool expanded = false;
await tester.pumpWidget(
new Material(
child: new Center(
wrap(
child: new ExpandIcon(
onPressed: (bool isExpanded) {
expanded = !expanded;
}
)
)
)
);
expect(expanded, isFalse);
@ -31,13 +29,11 @@ void main() {
testWidgets('ExpandIcon disabled', (WidgetTester tester) async {
await tester.pumpWidget(
const Material(
child: const Center(
wrap(
child: const ExpandIcon(
onPressed: null
)
)
)
);
final IconTheme iconTheme = tester.firstWidget(find.byType(IconTheme));
@ -48,8 +44,7 @@ void main() {
bool expanded = false;
await tester.pumpWidget(
new Material(
child: new Center(
wrap(
child: new ExpandIcon(
isExpanded: false,
onPressed: (bool isExpanded) {
@ -57,12 +52,10 @@ void main() {
}
)
)
)
);
await tester.pumpWidget(
new Material(
child: new Center(
wrap(
child: new ExpandIcon(
isExpanded: true,
onPressed: (bool isExpanded) {
@ -70,9 +63,17 @@ void main() {
}
)
)
)
);
expect(expanded, isFalse);
});
}
Widget wrap({ Widget child }) {
return new Directionality(
textDirection: TextDirection.ltr,
child: new Center(
child: new Material(child: child),
),
);
}

View File

@ -154,7 +154,7 @@ class TestWidget extends StatelessWidget {
return new GestureDetector(
onTap: tapHandler(context),
onLongPress: longPressHandler(context),
child: const Text('X'),
child: const Text('X', textDirection: TextDirection.ltr),
);
}
}

View File

@ -6,11 +6,12 @@ import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Floating Action Button control test',
(WidgetTester tester) async {
testWidgets('Floating Action Button control test', (WidgetTester tester) async {
bool didPressButton = false;
await tester.pumpWidget(
new Center(
new Directionality(
textDirection: TextDirection.ltr,
child: new Center(
child: new FloatingActionButton(
onPressed: () {
didPressButton = true;
@ -18,6 +19,7 @@ void main() {
child: const Icon(Icons.add),
),
),
),
);
expect(didPressButton, isFalse);

View File

@ -38,7 +38,12 @@ void main() {
expect(tester.getBottomLeft(find.byKey(headerKey)).dy,
lessThan(tester.getTopLeft(find.byKey(footerKey)).dy));
await tester.pumpWidget(const GridTile(child: const Text('Simple')));
await tester.pumpWidget(
const Directionality(
textDirection: TextDirection.ltr,
child: const GridTile(child: const Text('Simple')),
),
);
expect(find.text('Simple'), findsOneWidget);
});

View File

@ -24,14 +24,12 @@ void main() {
testWidgets('test default icon buttons are sized up to 48', (WidgetTester tester) async {
await tester.pumpWidget(
new Material(
child: new Center(
wrap(
child: new IconButton(
onPressed: mockOnPressedFunction,
icon: const Icon(Icons.link),
),
),
),
);
final RenderBox iconButton = tester.renderObject(find.byType(IconButton));
@ -43,15 +41,13 @@ void main() {
testWidgets('test small icons are sized up to 48dp', (WidgetTester tester) async {
await tester.pumpWidget(
new Material(
child: new Center(
wrap(
child: new IconButton(
iconSize: 10.0,
onPressed: mockOnPressedFunction,
icon: const Icon(Icons.link),
),
),
),
);
final RenderBox iconButton = tester.renderObject(find.byType(IconButton));
@ -60,8 +56,7 @@ void main() {
testWidgets('test icons can be small when total size is >48dp', (WidgetTester tester) async {
await tester.pumpWidget(
new Material(
child: new Center(
wrap(
child: new IconButton(
iconSize: 10.0,
padding: const EdgeInsets.all(30.0),
@ -69,7 +64,6 @@ void main() {
icon: const Icon(Icons.link),
),
),
),
);
final RenderBox iconButton = tester.renderObject(find.byType(IconButton));
@ -78,8 +72,7 @@ void main() {
testWidgets('test default icon buttons are constrained', (WidgetTester tester) async {
await tester.pumpWidget(
new Material(
child: new Center(
wrap(
child: new IconButton(
padding: EdgeInsets.zero,
onPressed: mockOnPressedFunction,
@ -87,7 +80,6 @@ void main() {
iconSize: 80.0,
),
),
),
);
final RenderBox box = tester.renderObject(find.byType(IconButton));
@ -120,15 +112,13 @@ void main() {
testWidgets('test default padding', (WidgetTester tester) async {
await tester.pumpWidget(
new Material(
child: new Center(
wrap(
child: new IconButton(
onPressed: mockOnPressedFunction,
icon: const Icon(Icons.ac_unit),
iconSize: 80.0,
),
),
),
);
final RenderBox box = tester.renderObject(find.byType(IconButton));
@ -203,15 +193,13 @@ void main() {
final Color directSplashColor = const Color(0xFF00000F);
final Color directHighlightColor = const Color(0xFF0000F0);
Widget buttonWidget = new Material(
child: new Center(
Widget buttonWidget = wrap(
child: new IconButton(
icon: const Icon(Icons.android),
splashColor: directSplashColor,
highlightColor: directHighlightColor,
onPressed: () { /* enable the button */ },
),
),
);
await tester.pumpWidget(
@ -236,13 +224,11 @@ void main() {
final Color themeSplashColor1 = const Color(0xFF000F00);
final Color themeHighlightColor1 = const Color(0xFF00FF00);
buttonWidget = new Material(
child: new Center(
buttonWidget = wrap(
child: new IconButton(
icon: const Icon(Icons.android),
onPressed: () { /* enable the button */ },
),
),
);
await tester.pumpWidget(
@ -285,3 +271,12 @@ void main() {
await gesture.up();
});
}
Widget wrap({ Widget child }) {
return new Directionality(
textDirection: TextDirection.ltr,
child: new Material(
child: new Center(child: child),
),
);
}

View File

@ -23,9 +23,8 @@ void main() {
testWidgets('RefreshIndicator', (WidgetTester tester) async {
refreshCalled = false;
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new RefreshIndicator(
new MaterialApp(
home: new RefreshIndicator(
onRefresh: refresh,
child: new ListView(
physics: const AlwaysScrollableScrollPhysics(),
@ -51,9 +50,8 @@ void main() {
testWidgets('RefreshIndicator - bottom', (WidgetTester tester) async {
refreshCalled = false;
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new RefreshIndicator(
new MaterialApp(
home: new RefreshIndicator(
onRefresh: refresh,
child: new ListView(
reverse: true,
@ -80,9 +78,8 @@ void main() {
testWidgets('RefreshIndicator - top - position', (WidgetTester tester) async {
refreshCalled = false;
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new RefreshIndicator(
new MaterialApp(
home: new RefreshIndicator(
onRefresh: holdRefresh,
child: new ListView(
physics: const AlwaysScrollableScrollPhysics(),
@ -107,9 +104,8 @@ void main() {
testWidgets('RefreshIndicator - bottom - position', (WidgetTester tester) async {
refreshCalled = false;
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new RefreshIndicator(
new MaterialApp(
home: new RefreshIndicator(
onRefresh: holdRefresh,
child: new ListView(
reverse: true,
@ -135,9 +131,8 @@ void main() {
testWidgets('RefreshIndicator - no movement', (WidgetTester tester) async {
refreshCalled = false;
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new RefreshIndicator(
new MaterialApp(
home: new RefreshIndicator(
onRefresh: refresh,
child: new ListView(
physics: const AlwaysScrollableScrollPhysics(),
@ -164,9 +159,8 @@ void main() {
testWidgets('RefreshIndicator - not enough', (WidgetTester tester) async {
refreshCalled = false;
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new RefreshIndicator(
new MaterialApp(
home: new RefreshIndicator(
onRefresh: refresh,
child: new ListView(
physics: const AlwaysScrollableScrollPhysics(),
@ -192,9 +186,8 @@ void main() {
testWidgets('RefreshIndicator - show - slow', (WidgetTester tester) async {
refreshCalled = false;
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new RefreshIndicator(
new MaterialApp(
home: new RefreshIndicator(
onRefresh: holdRefresh, // this one never returns
child: new ListView(
physics: const AlwaysScrollableScrollPhysics(),
@ -236,9 +229,8 @@ void main() {
testWidgets('RefreshIndicator - show - fast', (WidgetTester tester) async {
refreshCalled = false;
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new RefreshIndicator(
new MaterialApp(
home: new RefreshIndicator(
onRefresh: refresh,
child: new ListView(
physics: const AlwaysScrollableScrollPhysics(),
@ -281,9 +273,8 @@ void main() {
testWidgets('RefreshIndicator - show - fast - twice', (WidgetTester tester) async {
refreshCalled = false;
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new RefreshIndicator(
new MaterialApp(
home: new RefreshIndicator(
onRefresh: refresh,
child: new ListView(
physics: const AlwaysScrollableScrollPhysics(),

View File

@ -1260,7 +1260,7 @@ void main() {
testWidgets('Cannot enter new lines onto single line TextField', (WidgetTester tester) async {
final TextEditingController textController = new TextEditingController();
await tester.pumpWidget(new Material(
await tester.pumpWidget(boilerplate(
child: new TextField(controller: textController, decoration: null),
));
@ -1272,7 +1272,7 @@ void main() {
testWidgets('Injected formatters are chained', (WidgetTester tester) async {
final TextEditingController textController = new TextEditingController();
await tester.pumpWidget(new Material(
await tester.pumpWidget(boilerplate(
child: new TextField(
controller: textController,
decoration: null,
@ -1293,7 +1293,7 @@ void main() {
testWidgets('Chained formatters are in sequence', (WidgetTester tester) async {
final TextEditingController textController = new TextEditingController();
await tester.pumpWidget(new Material(
await tester.pumpWidget(boilerplate(
child: new TextField(
controller: textController,
decoration: null,

View File

@ -32,7 +32,9 @@ void main() {
testWidgets('Does tooltip end up in the right place - center', (WidgetTester tester) async {
final GlobalKey key = new GlobalKey();
await tester.pumpWidget(
new Overlay(
new Directionality(
textDirection: TextDirection.ltr,
child: new Overlay(
initialEntries: <OverlayEntry>[
new OverlayEntry(
builder: (BuildContext context) {
@ -50,16 +52,17 @@ void main() {
preferBelow: false,
child: new Container(
width: 0.0,
height: 0.0
)
)
height: 0.0,
),
]
),
),
],
);
}
},
),
],
),
),
]
)
);
(key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
@ -85,7 +88,9 @@ void main() {
testWidgets('Does tooltip end up in the right place - top left', (WidgetTester tester) async {
final GlobalKey key = new GlobalKey();
await tester.pumpWidget(
new Overlay(
new Directionality(
textDirection: TextDirection.ltr,
child: new Overlay(
initialEntries: <OverlayEntry>[
new OverlayEntry(
builder: (BuildContext context) {
@ -103,16 +108,17 @@ void main() {
preferBelow: false,
child: new Container(
width: 0.0,
height: 0.0
)
)
height: 0.0,
),
]
),
),
],
);
}
},
),
],
),
),
]
)
);
(key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
@ -134,7 +140,9 @@ void main() {
testWidgets('Does tooltip end up in the right place - center prefer above fits', (WidgetTester tester) async {
final GlobalKey key = new GlobalKey();
await tester.pumpWidget(
new Overlay(
new Directionality(
textDirection: TextDirection.ltr,
child: new Overlay(
initialEntries: <OverlayEntry>[
new OverlayEntry(
builder: (BuildContext context) {
@ -152,16 +160,17 @@ void main() {
preferBelow: false,
child: new Container(
width: 0.0,
height: 0.0
)
)
height: 0.0,
),
]
),
),
],
);
}
},
),
],
),
),
]
)
);
(key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
@ -185,7 +194,9 @@ void main() {
testWidgets('Does tooltip end up in the right place - center prefer above does not fit', (WidgetTester tester) async {
final GlobalKey key = new GlobalKey();
await tester.pumpWidget(
new Overlay(
new Directionality(
textDirection: TextDirection.ltr,
child: new Overlay(
initialEntries: <OverlayEntry>[
new OverlayEntry(
builder: (BuildContext context) {
@ -203,16 +214,17 @@ void main() {
preferBelow: false,
child: new Container(
width: 0.0,
height: 0.0
)
)
height: 0.0,
),
]
),
),
],
);
}
},
),
],
),
),
]
)
);
(key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
@ -247,7 +259,9 @@ void main() {
testWidgets('Does tooltip end up in the right place - center prefer below fits', (WidgetTester tester) async {
final GlobalKey key = new GlobalKey();
await tester.pumpWidget(
new Overlay(
new Directionality(
textDirection: TextDirection.ltr,
child: new Overlay(
initialEntries: <OverlayEntry>[
new OverlayEntry(
builder: (BuildContext context) {
@ -265,16 +279,17 @@ void main() {
preferBelow: true,
child: new Container(
width: 0.0,
height: 0.0
)
)
height: 0.0,
),
]
),
),
],
);
}
},
),
],
),
),
]
)
);
(key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
@ -297,7 +312,9 @@ void main() {
testWidgets('Does tooltip end up in the right place - way off to the right', (WidgetTester tester) async {
final GlobalKey key = new GlobalKey();
await tester.pumpWidget(
new Overlay(
new Directionality(
textDirection: TextDirection.ltr,
child: new Overlay(
initialEntries: <OverlayEntry>[
new OverlayEntry(
builder: (BuildContext context) {
@ -315,16 +332,17 @@ void main() {
preferBelow: true,
child: new Container(
width: 0.0,
height: 0.0
)
)
height: 0.0,
),
]
),
),
],
);
}
},
),
],
),
),
]
)
);
(key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
@ -349,7 +367,9 @@ void main() {
testWidgets('Does tooltip end up in the right place - near the edge', (WidgetTester tester) async {
final GlobalKey key = new GlobalKey();
await tester.pumpWidget(
new Overlay(
new Directionality(
textDirection: TextDirection.ltr,
child: new Overlay(
initialEntries: <OverlayEntry>[
new OverlayEntry(
builder: (BuildContext context) {
@ -367,16 +387,17 @@ void main() {
preferBelow: true,
child: new Container(
width: 0.0,
height: 0.0
)
)
height: 0.0,
),
]
),
),
],
);
}
},
),
],
),
),
]
)
);
(key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
@ -438,7 +459,9 @@ void main() {
final GlobalKey key = new GlobalKey();
await tester.pumpWidget(
new Overlay(
new Directionality(
textDirection: TextDirection.ltr,
child: new Overlay(
initialEntries: <OverlayEntry>[
new OverlayEntry(
builder: (BuildContext context) {
@ -450,15 +473,16 @@ void main() {
child: new Tooltip(
key: key,
message: tooltipText,
child: new Container(width: 0.0, height: 0.0)
)
child: new Container(width: 0.0, height: 0.0),
),
]
),
],
);
}
},
),
],
),
),
]
)
);
expect(semantics, hasSemantics(new TestSemantics.root(label: tooltipText)));
@ -504,7 +528,8 @@ void main() {
testWidgets('Haptic feedback', (WidgetTester tester) async {
final FeedbackTester feedback = new FeedbackTester();
await tester.pumpWidget(new MaterialApp(
await tester.pumpWidget(
new MaterialApp(
home: new Center(
child: new Tooltip(
message: 'Foo',
@ -512,10 +537,10 @@ void main() {
width: 100.0,
height: 100.0,
color: Colors.green[500],
)
)
)
)
),
),
),
),
);
await tester.longPress(find.byType(Tooltip));

View File

@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:io' as io;
import 'dart:ui' as ui;
import 'package:flutter/painting.dart';
@ -10,7 +9,8 @@ import 'package:flutter_test/flutter_test.dart';
void main() {
test('TextPainter caret test', () {
final TextPainter painter = new TextPainter();
final TextPainter painter = new TextPainter()
..textDirection = TextDirection.ltr;
String text = 'A';
painter.text = new TextSpan(text: text);
@ -27,13 +27,20 @@ void main() {
painter.layout();
caretOffset = painter.getOffsetForCaret(new ui.TextPosition(offset: text.length), ui.Rect.zero);
expect(caretOffset.dx, painter.width);
}, skip: io.Platform.isMacOS); // TODO(goderbauer): Disabled because of https://github.com/flutter/flutter/issues/4273
});
test('TextPainter error test', () {
final TextPainter painter = new TextPainter();
final TextPainter painter = new TextPainter(textDirection: TextDirection.ltr);
expect(() { painter.paint(null, Offset.zero); }, throwsFlutterError);
});
test('TextPainter requires textDirection', () {
final TextPainter painter1 = new TextPainter(text: const TextSpan(text: ''));
expect(() { painter1.layout(); }, throwsAssertionError);
final TextPainter painter2 = new TextPainter(text: const TextSpan(text: ''), textDirection: TextDirection.rtl);
expect(() { painter2.layout(); }, isNot(throwsException));
});
test('TextPainter size test', () {
final TextPainter painter = new TextPainter(
text: const TextSpan(
@ -44,20 +51,27 @@ void main() {
fontSize: 123.0,
),
),
textDirection: TextDirection.ltr,
);
painter.layout();
expect(painter.size, const Size(123.0, 123.0));
});
test('TextPainter default text height is 14 pixels', () {
final TextPainter painter = new TextPainter(text: const TextSpan(text: 'x'));
final TextPainter painter = new TextPainter(
text: const TextSpan(text: 'x'),
textDirection: TextDirection.ltr,
);
painter.layout();
expect(painter.preferredLineHeight, 14.0);
expect(painter.size, const Size(14.0, 14.0));
});
test('TextPainter sets paragraph size from root', () {
final TextPainter painter = new TextPainter(text: const TextSpan(text: 'x', style: const TextStyle(fontSize: 100.0)));
final TextPainter painter = new TextPainter(
text: const TextSpan(text: 'x', style: const TextStyle(fontSize: 100.0)),
textDirection: TextDirection.ltr,
);
painter.layout();
expect(painter.preferredLineHeight, 100.0);
expect(painter.size, const Size(100.0, 100.0));

View File

@ -8,7 +8,7 @@ import 'package:flutter/painting.dart';
import 'package:test/test.dart';
void main() {
test("TextStyle control test", () {
test('TextStyle control test', () {
expect(
const TextStyle(inherit: false).toString(),
equals('TextStyle(inherit: false, <no style specified>)'),
@ -117,9 +117,17 @@ void main() {
final ui.ParagraphStyle ps2 = s2.getParagraphStyle(textAlign: TextAlign.center);
expect(ps2, equals(new ui.ParagraphStyle(textAlign: TextAlign.center, fontWeight: FontWeight.w800, fontSize: 10.0, lineHeight: 100.0)));
expect(ps2.toString(), 'ParagraphStyle(textAlign: TextAlign.center, fontWeight: FontWeight.w800, fontStyle: unspecified, maxLines: unspecified, fontFamily: unspecified, fontSize: 10.0, lineHeight: 100.0x, ellipsis: unspecified)');
expect(ps2.toString(), 'ParagraphStyle(textAlign: TextAlign.center, textDirection: unspecified, fontWeight: FontWeight.w800, fontStyle: unspecified, maxLines: unspecified, fontFamily: unspecified, fontSize: 10.0, lineHeight: 100.0x, ellipsis: unspecified)');
final ui.ParagraphStyle ps5 = s5.getParagraphStyle();
expect(ps5, equals(new ui.ParagraphStyle(fontWeight: FontWeight.w700, fontSize: 12.0, lineHeight: 123.0)));
expect(ps5.toString(), 'ParagraphStyle(textAlign: unspecified, fontWeight: FontWeight.w700, fontStyle: unspecified, maxLines: unspecified, fontFamily: unspecified, fontSize: 12.0, lineHeight: 123.0x, ellipsis: unspecified)');
expect(ps5.toString(), 'ParagraphStyle(textAlign: unspecified, textDirection: unspecified, fontWeight: FontWeight.w700, fontStyle: unspecified, maxLines: unspecified, fontFamily: unspecified, fontSize: 12.0, lineHeight: 123.0x, ellipsis: unspecified)');
final ui.ParagraphStyle ps6 = const TextStyle().getParagraphStyle(textDirection: TextDirection.ltr);
expect(ps6, equals(new ui.ParagraphStyle(textDirection: TextDirection.ltr)));
expect(ps6.toString(), 'ParagraphStyle(textAlign: unspecified, textDirection: TextDirection.ltr, fontWeight: unspecified, fontStyle: unspecified, maxLines: unspecified, fontFamily: unspecified, fontSize: unspecified, lineHeight: unspecified, ellipsis: unspecified)');
final ui.ParagraphStyle ps7 = const TextStyle().getParagraphStyle(textDirection: TextDirection.rtl);
expect(ps7, equals(new ui.ParagraphStyle(textDirection: TextDirection.rtl)));
expect(ps7.toString(), 'ParagraphStyle(textAlign: unspecified, textDirection: TextDirection.rtl, fontWeight: unspecified, fontStyle: unspecified, maxLines: unspecified, fontFamily: unspecified, fontSize: unspecified, lineHeight: unspecified, ellipsis: unspecified)');
});
}

View File

@ -73,8 +73,8 @@ void main() {
expect(coloredBox, hasAGoodToStringDeep);
expect(coloredBox.toStringDeep(), equalsIgnoringHashCodes(
'RenderDecoratedBox#00000 NEEDS-LAYOUT NEEDS-PAINT DETACHED\n'
' parentData: null\n'
' constraints: null\n'
' parentData: MISSING\n'
' constraints: MISSING\n'
' size: MISSING\n'
' decoration: BoxDecoration:\n'
' <no decorations specified>\n'

View File

@ -12,6 +12,8 @@ void main() {
style: const TextStyle(height: 1.0, fontSize: 10.0, fontFamily: 'Ahem'),
text: '12345',
),
textAlign: TextAlign.start,
textDirection: TextDirection.ltr,
offset: new ViewportOffset.zero(),
);
expect(editable.getMinIntrinsicWidth(double.INFINITY), 50.0);
@ -23,8 +25,8 @@ void main() {
editable.toStringDeep(),
equalsIgnoringHashCodes(
'RenderEditable#00000 NEEDS-LAYOUT NEEDS-PAINT DETACHED\n'
' │ parentData: null\n'
' │ constraints: null\n'
' │ parentData: MISSING\n'
' │ constraints: MISSING\n'
' │ size: MISSING\n'
' │ cursorColor: null\n'
' │ showCursor: ValueNotifier<bool>#00000(false)\n'

View File

@ -91,8 +91,8 @@ void main() {
flex.toStringDeep(),
equalsIgnoringHashCodes(
'RenderFlex#00000 NEEDS-LAYOUT NEEDS-PAINT DETACHED\n'
' parentData: null\n'
' constraints: null\n'
' parentData: MISSING\n'
' constraints: MISSING\n'
' size: MISSING\n'
' direction: horizontal\n'
' mainAxisAlignment: start\n'

View File

@ -9,13 +9,16 @@ import 'package:test/test.dart';
import 'rendering_tester.dart';
void main() {
test("overflow should not affect baseline", () {
test('overflow should not affect baseline', () {
RenderBox root, child, text;
double baseline1, baseline2, height1, height2;
root = new RenderPositionedBox(
child: new RenderCustomPaint(
child: child = text = new RenderParagraph(const TextSpan(text: 'Hello World')),
child: child = text = new RenderParagraph(
const TextSpan(text: 'Hello World'),
textDirection: TextDirection.ltr,
),
painter: new TestCallbackPainter(
onPaint: () {
baseline1 = child.getDistanceToBaseline(TextBaseline.alphabetic);
@ -29,7 +32,10 @@ void main() {
root = new RenderPositionedBox(
child: new RenderCustomPaint(
child: child = new RenderConstrainedOverflowBox(
child: text = new RenderParagraph(const TextSpan(text: 'Hello World')),
child: text = new RenderParagraph(
const TextSpan(text: 'Hello World'),
textDirection: TextDirection.ltr,
),
maxHeight: height1 / 2.0,
alignment: const FractionalOffset(0.0, 0.0)
),

View File

@ -10,8 +10,9 @@ void main() {
final RenderParagraph paragraph = new RenderParagraph(
const TextSpan(
style: const TextStyle(height: 1.0),
text: 'Hello World'
)
text: 'Hello World',
),
textDirection: TextDirection.ltr,
);
final RenderListBody testBlock = new RenderListBody(
children: <RenderBox>[

View File

@ -14,7 +14,10 @@ const String _kText = 'I polished up that handle so carefullee\nThat now I am th
void main() {
test('getOffsetForCaret control test', () {
final RenderParagraph paragraph = new RenderParagraph(const TextSpan(text: _kText));
final RenderParagraph paragraph = new RenderParagraph(
const TextSpan(text: _kText),
textDirection: TextDirection.ltr,
);
layout(paragraph);
final Rect caret = new Rect.fromLTWH(0.0, 0.0, 2.0, 20.0);
@ -30,7 +33,10 @@ void main() {
});
test('getPositionForOffset control test', () {
final RenderParagraph paragraph = new RenderParagraph(const TextSpan(text: _kText));
final RenderParagraph paragraph = new RenderParagraph(
const TextSpan(text: _kText),
textDirection: TextDirection.ltr,
);
layout(paragraph);
final TextPosition position20 = paragraph.getPositionForOffset(const Offset(20.0, 5.0));
@ -44,7 +50,10 @@ void main() {
});
test('getBoxesForSelection control test', () {
final RenderParagraph paragraph = new RenderParagraph(const TextSpan(text: _kText));
final RenderParagraph paragraph = new RenderParagraph(
const TextSpan(text: _kText),
textDirection: TextDirection.ltr,
);
layout(paragraph);
List<ui.TextBox> boxes = paragraph.getBoxesForSelection(
@ -61,7 +70,10 @@ void main() {
});
test('getWordBoundary control test', () {
final RenderParagraph paragraph = new RenderParagraph(const TextSpan(text: _kText));
final RenderParagraph paragraph = new RenderParagraph(
const TextSpan(text: _kText),
textDirection: TextDirection.ltr,
);
layout(paragraph);
final TextRange range5 = paragraph.getWordBoundary(const TextPosition(offset: 5));
@ -81,6 +93,7 @@ void main() {
'is a wrapping test. It should wrap at manual newlines, and if softWrap is true, also at spaces.',
style: const TextStyle(fontFamily: 'Ahem', fontSize: 10.0),
),
textDirection: TextDirection.ltr,
maxLines: 1,
softWrap: true,
);
@ -157,6 +170,7 @@ void main() {
// 0 1 2 3 4 5 6 7 8 9 10 11 12
style: const TextStyle(fontFamily: 'Ahem', fontSize: 10.0),
),
textDirection: TextDirection.ltr,
);
layout(paragraph, constraints: const BoxConstraints(maxWidth: 100.0));
void layoutAt(int maxLines) {
@ -183,6 +197,7 @@ void main() {
text: 'Hello',
style: const TextStyle(color: const Color(0xFF000000)),
),
textDirection: TextDirection.ltr,
);
layout(paragraph, constraints: const BoxConstraints(maxWidth: 100.0), phase: EnginePhase.paint);
expect(paragraph.debugNeedsLayout, isFalse);
@ -210,15 +225,21 @@ void main() {
test('toStringDeep', () {
final RenderParagraph paragraph = new RenderParagraph(
const TextSpan(text: _kText),
textDirection: TextDirection.ltr,
);
expect(paragraph, hasAGoodToStringDeep);
expect(
paragraph.toStringDeep(),
equalsIgnoringHashCodes(
'RenderParagraph#00000 NEEDS-LAYOUT NEEDS-PAINT DETACHED\n'
' │ parentData: null\n'
' │ constraints: null\n'
' │ parentData: MISSING\n'
' │ constraints: MISSING\n'
' │ size: MISSING\n'
' │ textAlign: start\n'
' │ textDirection: ltr\n'
' │ softWrap: wrapping at box width\n'
' │ overflow: clip\n'
' │ maxLines: unlimited\n'
' ╘═╦══ text ═══\n'
' ║ TextSpan:\n'
' ║ "I polished up that handle so carefullee\n'

View File

@ -16,7 +16,12 @@ int countSemanticsChildren(RenderObject object) {
void main() {
test('RenderOpacity and children and semantics', () {
final RenderOpacity box = new RenderOpacity(child: new RenderParagraph(const TextSpan()));
final RenderOpacity box = new RenderOpacity(
child: new RenderParagraph(
const TextSpan(),
textDirection: TextDirection.ltr,
),
);
expect(countSemanticsChildren(box), 1);
box.opacity = 0.5;
expect(countSemanticsChildren(box), 1);

View File

@ -19,8 +19,8 @@ void main() {
root.toStringDeep(),
equalsIgnoringHashCodes(
'RenderViewport#00000 NEEDS-LAYOUT NEEDS-PAINT DETACHED\n'
' parentData: null\n'
' constraints: null\n'
' parentData: MISSING\n'
' constraints: MISSING\n'
' size: MISSING\n'
' axisDirection: down\n'
' crossAxisDirection: right\n'

View File

@ -13,8 +13,8 @@ void main() {
renderWrap.toStringDeep(),
equalsIgnoringHashCodes(
'RenderWrap#00000 NEEDS-LAYOUT NEEDS-PAINT DETACHED\n'
' parentData: null\n'
' constraints: null\n'
' parentData: MISSING\n'
' constraints: MISSING\n'
' size: MISSING\n'
' direction: horizontal\n'
' alignment: start\n'

View File

@ -230,7 +230,7 @@ void main() {
duration: const Duration(milliseconds: 200),
width: 100.0,
height: 100.0,
child: const Text('X')
child: const Text('X', textDirection: TextDirection.ltr)
)
)
);
@ -250,7 +250,7 @@ void main() {
duration: const Duration(milliseconds: 200),
width: 200.0,
height: 200.0,
child: const Text('X')
child: const Text('X', textDirection: TextDirection.ltr)
)
)
);
@ -274,7 +274,7 @@ void main() {
duration: const Duration(milliseconds: 200),
width: 200.0,
height: 100.0,
child: const Text('X')
child: const Text('X', textDirection: TextDirection.ltr)
)
)
);

View File

@ -302,16 +302,16 @@ void main() {
testWidgets('AnimatedCrossFade.layoutBuilder', (WidgetTester tester) async {
await tester.pumpWidget(const AnimatedCrossFade(
firstChild: const Text('AAA'),
secondChild: const Text('BBB'),
firstChild: const Text('AAA', textDirection: TextDirection.ltr),
secondChild: const Text('BBB', textDirection: TextDirection.ltr),
crossFadeState: CrossFadeState.showFirst,
duration: const Duration(milliseconds: 50),
));
expect(find.text('AAA'), findsOneWidget);
expect(find.text('BBB'), findsOneWidget);
await tester.pumpWidget(new AnimatedCrossFade(
firstChild: const Text('AAA'),
secondChild: const Text('BBB'),
firstChild: const Text('AAA', textDirection: TextDirection.ltr),
secondChild: const Text('BBB', textDirection: TextDirection.ltr),
crossFadeState: CrossFadeState.showFirst,
duration: const Duration(milliseconds: 50),
layoutBuilder: (Widget a, Key aKey, Widget b, Key bKey) => a,
@ -319,8 +319,8 @@ void main() {
expect(find.text('AAA'), findsOneWidget);
expect(find.text('BBB'), findsNothing);
await tester.pumpWidget(new AnimatedCrossFade(
firstChild: const Text('AAA'),
secondChild: const Text('BBB'),
firstChild: const Text('AAA', textDirection: TextDirection.ltr),
secondChild: const Text('BBB', textDirection: TextDirection.ltr),
crossFadeState: CrossFadeState.showSecond,
duration: const Duration(milliseconds: 50),
layoutBuilder: (Widget a, Key aKey, Widget b, Key bKey) => a,

View File

@ -121,7 +121,7 @@ void main() {
child: new SizedBox(
height: 100.0,
child: new Center(
child: new Text('item $item'),
child: new Text('item $item', textDirection: TextDirection.ltr),
),
),
);

View File

@ -9,7 +9,7 @@ import 'package:flutter_test/flutter_test.dart';
void main() {
Widget snapshotText(BuildContext context, AsyncSnapshot<String> snapshot) {
return new Text(snapshot.toString());
return new Text(snapshot.toString(), textDirection: TextDirection.ltr);
}
group('AsyncSnapshot', () {
test('requiring data succeeds if data is present', () {
@ -280,5 +280,5 @@ class StringCollector extends StreamBuilderBase<String, List<String>> {
List<String> afterDisconnected(List<String> current) => current..add('disc');
@override
Widget build(BuildContext context, List<String> currentSummary) => new Text(currentSummary.join(', '));
Widget build(BuildContext context, List<String> currentSummary) => new Text(currentSummary.join(', '), textDirection: TextDirection.ltr);
}

View File

@ -19,7 +19,8 @@ class TestCanvas implements Canvas {
void main() {
test('A Banner with a location of topLeft paints in the top left', () {
final BannerPainter bannerPainter = new BannerPainter(
message:"foo",
message: 'foo',
textDirection: TextDirection.ltr,
location: BannerLocation.topLeft
);
@ -45,7 +46,8 @@ void main() {
test('A Banner with a location of topRight paints in the top right', () {
final BannerPainter bannerPainter = new BannerPainter(
message:"foo",
message: 'foo',
textDirection: TextDirection.ltr,
location: BannerLocation.topRight
);
@ -71,7 +73,8 @@ void main() {
test('A Banner with a location of bottomLeft paints in the bottom left', () {
final BannerPainter bannerPainter = new BannerPainter(
message:"foo",
message: 'foo',
textDirection: TextDirection.ltr,
location: BannerLocation.bottomLeft
);
@ -97,7 +100,8 @@ void main() {
test('A Banner with a location of bottomRight paints in the bottom right', () {
final BannerPainter bannerPainter = new BannerPainter(
message:"foo",
message: 'foo',
textDirection: TextDirection.ltr,
location: BannerLocation.bottomRight
);

View File

@ -14,7 +14,7 @@ void main() {
fontFamily: 'Ahem',
fontSize: 100.0,
),
child: const Text('X'),
child: const Text('X', textDirection: TextDirection.ltr),
),
),
);
@ -32,7 +32,7 @@ void main() {
fontFamily: 'Ahem',
fontSize: 100.0,
),
child: const Text('X'),
child: const Text('X', textDirection: TextDirection.ltr),
),
),
),

View File

@ -21,9 +21,9 @@ Widget buildFrame(ScrollPhysics physics) {
crossAxisAlignment: CrossAxisAlignment.start,
textDirection: TextDirection.ltr,
children: <Widget>[
const SizedBox(height: 100.0, child: const Text('top')),
const SizedBox(height: 100.0, child: const Text('top', textDirection: TextDirection.ltr)),
new Expanded(child: new Container()),
const SizedBox(height: 100.0, child: const Text('bottom')),
const SizedBox(height: 100.0, child: const Text('bottom', textDirection: TextDirection.ltr)),
],
),
),

View File

@ -7,7 +7,7 @@ import 'package:flutter/widgets.dart';
void main() {
testWidgets('DefaultTextStyle changes propagate to Text', (WidgetTester tester) async {
const Text textWidget = const Text('Hello');
const Text textWidget = const Text('Hello', textDirection: TextDirection.ltr);
const TextStyle s1 = const TextStyle(
fontSize: 10.0,
fontWeight: FontWeight.w800,
@ -16,7 +16,7 @@ void main() {
await tester.pumpWidget(const DefaultTextStyle(
style: s1,
child: textWidget
child: textWidget,
));
RichText text = tester.firstWidget(find.byType(RichText));

View File

@ -14,7 +14,9 @@ List<int> dismissedItems = <int>[];
Widget background;
Widget buildTest({ double startToEndThreshold }) {
return new StatefulBuilder(
return new Directionality(
textDirection: TextDirection.ltr,
child: new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
Widget buildDismissibleItem(int item) {
return new Dismissible(
@ -56,6 +58,7 @@ Widget buildTest({ double startToEndThreshold }) {
),
);
},
),
);
}
@ -287,7 +290,10 @@ void main() {
// actually remove the dismissed widget, which is a violation of the
// Dismissible contract. This is not an example of good practice.
testWidgets('dismissing bottom then top (smoketest)', (WidgetTester tester) async {
await tester.pumpWidget(new Center(
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Center(
child: new Container(
width: 100.0,
height: 1000.0,
@ -298,7 +304,9 @@ void main() {
],
),
),
));
),
),
);
expect(find.text('1'), findsOneWidget);
expect(find.text('2'), findsOneWidget);
await dismissElement(tester, find.text('2'), gestureDirection: DismissDirection.startToEnd);

View File

@ -24,7 +24,7 @@ class TestWidgetState extends State<TestWidget> {
}
@override
Widget build(BuildContext context) => const Text('test');
Widget build(BuildContext context) => const Text('test', textDirection: TextDirection.ltr);
}
void main() {

View File

@ -28,7 +28,7 @@ void main() {
width: 100.0,
height: 100.0,
child: const Center(
child: const Text('X'),
child: const Text('X', textDirection: TextDirection.ltr),
),
),
),
@ -63,8 +63,8 @@ void main() {
new Row(
textDirection: TextDirection.ltr,
children: <Widget>[
const Expanded(flex: null, child: const Text('one')),
const Flexible(flex: null, child: const Text('two')),
const Expanded(flex: null, child: const Text('one', textDirection: TextDirection.ltr)),
const Flexible(flex: null, child: const Text('two', textDirection: TextDirection.ltr)),
],
),
);

View File

@ -44,7 +44,7 @@ void main() {
width: 100.0,
height: 100.0,
color: const Color(0xFF0000FF),
child: new Text('$i')
child: new Text('$i', textDirection: TextDirection.ltr)
)
);
}

View File

@ -47,7 +47,7 @@ class TestFocusableState extends State<TestFocusable> {
child: new AnimatedBuilder(
animation: focusNode,
builder: (BuildContext context, Widget child) {
return new Text(focusNode.hasFocus ? widget.yes : widget.no);
return new Text(focusNode.hasFocus ? widget.yes : widget.no, textDirection: TextDirection.ltr);
},
),
);

View File

@ -11,7 +11,9 @@ void main() {
String fieldValue;
Widget builder() {
return new Center(
return new Directionality(
textDirection: TextDirection.ltr,
child: new Center(
child: new Material(
child: new Form(
key: formKey,
@ -20,6 +22,7 @@ void main() {
),
),
),
),
);
}
@ -42,7 +45,9 @@ void main() {
String fieldValue;
Widget builder() {
return new Center(
return new Directionality(
textDirection: TextDirection.ltr,
child: new Center(
child: new Material(
child: new Form(
child: new TextField(
@ -50,6 +55,7 @@ void main() {
),
),
),
),
);
}
@ -72,7 +78,9 @@ void main() {
String errorText(String value) => value + '/error';
Widget builder(bool autovalidate) {
return new Center(
return new Directionality(
textDirection: TextDirection.ltr,
child: new Center(
child: new Material(
child: new Form(
key: formKey,
@ -82,6 +90,7 @@ void main() {
),
),
),
),
);
}
@ -162,7 +171,9 @@ void main() {
final GlobalKey<FormFieldState<String>> inputKey = new GlobalKey<FormFieldState<String>>();
Widget builder() {
return new Center(
return new Directionality(
textDirection: TextDirection.ltr,
child: new Center(
child: new Material(
child: new Form(
child: new TextFormField(
@ -171,6 +182,7 @@ void main() {
),
),
),
),
);
}
@ -198,7 +210,9 @@ void main() {
String fieldValue;
Widget builder(bool remove) {
return new Center(
return new Directionality(
textDirection: TextDirection.ltr,
child: new Center(
child: new Material(
child: new Form(
key: formKey,
@ -209,6 +223,7 @@ void main() {
),
),
),
),
);
}

View File

@ -25,7 +25,7 @@ class StatefulLeafState extends State<StatefulLeaf> {
void markNeedsBuild() { setState(() { }); }
@override
Widget build(BuildContext context) => const Text('leaf');
Widget build(BuildContext context) => const Text('leaf', textDirection: TextDirection.ltr);
}
class KeyedWrapper extends StatelessWidget {

View File

@ -8,7 +8,7 @@ import 'package:flutter/widgets.dart';
void main() {
testWidgets('toString control test', (WidgetTester tester) async {
await tester.pumpWidget(const Center(child: const Text('Hello')));
await tester.pumpWidget(const Center(child: const Text('Hello', textDirection: TextDirection.ltr)));
final HitTestResult result = tester.hitTestOnBinding(Offset.zero);
expect(result, hasOneLineDescription);
expect(result.path.first, hasOneLineDescription);

View File

@ -26,6 +26,7 @@ void main() {
new Center(
child: new RichText(
key: textKey,
textDirection: TextDirection.ltr,
text: new TextSpan(
children: <TextSpan>[
new TextSpan(

View File

@ -8,13 +8,16 @@ import 'package:flutter/widgets.dart';
void main() {
testWidgets('Can set opacity for an Icon', (WidgetTester tester) async {
await tester.pumpWidget(
const IconTheme(
const Directionality(
textDirection: TextDirection.ltr,
child: const IconTheme(
data: const IconThemeData(
color: const Color(0xFF666666),
opacity: 0.5
),
child: const Icon(const IconData(0xd0a0, fontFamily: 'Arial'))
)
),
),
);
final RichText text = tester.widget(find.byType(RichText));
expect(text.text.style.color, const Color(0xFF666666).withOpacity(0.5));
@ -22,9 +25,12 @@ void main() {
testWidgets('Icon sizing - no theme, default size', (WidgetTester tester) async {
await tester.pumpWidget(
const Center(
const Directionality(
textDirection: TextDirection.ltr,
child: const Center(
child: const Icon(null),
),
),
);
final RenderBox renderObject = tester.renderObject(find.byType(Icon));
@ -33,12 +39,15 @@ void main() {
testWidgets('Icon sizing - no theme, explicit size', (WidgetTester tester) async {
await tester.pumpWidget(
const Center(
const Directionality(
textDirection: TextDirection.ltr,
child: const Center(
child: const Icon(
null,
size: 96.0,
),
),
),
);
final RenderBox renderObject = tester.renderObject(find.byType(Icon));
@ -47,12 +56,15 @@ void main() {
testWidgets('Icon sizing - sized theme', (WidgetTester tester) async {
await tester.pumpWidget(
const Center(
const Directionality(
textDirection: TextDirection.ltr,
child: const Center(
child: const IconTheme(
data: const IconThemeData(size: 36.0),
child: const Icon(null),
),
),
),
);
final RenderBox renderObject = tester.renderObject(find.byType(Icon));
@ -61,7 +73,9 @@ void main() {
testWidgets('Icon sizing - sized theme, explicit size', (WidgetTester tester) async {
await tester.pumpWidget(
const Center(
const Directionality(
textDirection: TextDirection.ltr,
child: const Center(
child: const IconTheme(
data: const IconThemeData(size: 36.0),
child: const Icon(
@ -69,6 +83,7 @@ void main() {
size: 48.0,
),
),
),
)
);
@ -78,12 +93,15 @@ void main() {
testWidgets('Icon sizing - sizeless theme, default size', (WidgetTester tester) async {
await tester.pumpWidget(
const Center(
const Directionality(
textDirection: TextDirection.ltr,
child: const Center(
child: const IconTheme(
data: const IconThemeData(),
child: const Icon(null),
),
),
),
);
final RenderBox renderObject = tester.renderObject(find.byType(Icon));
@ -93,9 +111,12 @@ void main() {
testWidgets('Icon with custom font', (WidgetTester tester) async {
await tester.pumpWidget(
const Center(
const Directionality(
textDirection: TextDirection.ltr,
child: const Center(
child: const Icon(const IconData(0x41, fontFamily: 'Roboto')),
),
),
);
final RichText richText = tester.firstWidget(find.byType(RichText));

View File

@ -92,7 +92,7 @@ class TriggerableState extends State<TriggerableWidget> {
@override
Widget build(BuildContext context) {
widget.counter.count++;
return new Text("Bang $_count!");
return new Text("Bang $_count!", textDirection: TextDirection.ltr);
}
}

View File

@ -129,7 +129,7 @@ void main() {
builder: (BuildContext context) {
final ValueInherited v = context.inheritFromWidgetOfExactType(ValueInherited);
log.add('a: ${v.value}');
return const Text('');
return const Text('', textDirection: TextDirection.ltr);
}
)
)
@ -146,7 +146,7 @@ void main() {
builder: (BuildContext context) {
final ValueInherited v = context.inheritFromWidgetOfExactType(ValueInherited);
log.add('b: ${v.value}');
return const Text('');
return const Text('', textDirection: TextDirection.ltr);
}
)
)
@ -204,7 +204,7 @@ void main() {
builder: (BuildContext context) {
final ValueInherited v = context.inheritFromWidgetOfExactType(ValueInherited);
log.add('a: ${v.value}');
return const Text('');
return const Text('', textDirection: TextDirection.ltr);
}
)
)
@ -222,7 +222,7 @@ void main() {
builder: (BuildContext context) {
final ValueInherited v = context.inheritFromWidgetOfExactType(ValueInherited);
log.add('b: ${v.value}');
return const Text('');
return const Text('', textDirection: TextDirection.ltr);
}
)
)
@ -265,7 +265,7 @@ void main() {
builder: (BuildContext context) {
final ValueInherited v = context.inheritFromWidgetOfExactType(ValueInherited);
log.add(v.value);
return const Text('');
return const Text('', textDirection: TextDirection.ltr);
}
);
@ -336,7 +336,7 @@ void main() {
builder: (BuildContext context) {
final ValueInherited v = context.inheritFromWidgetOfExactType(ValueInherited);
log.add(v.value);
return const Text('');
return const Text('', textDirection: TextDirection.ltr);
}
);

View File

@ -66,7 +66,7 @@ void main() {
key: new ValueKey<int>(index),
width: 500.0, // this should be ignored
height: 400.0, // should be overridden by itemExtent
child: new Text('$index')
child: new Text('$index', textDirection: TextDirection.ltr)
);
};
@ -79,7 +79,7 @@ void main() {
itemExtent: 200.0,
itemBuilder: itemBuilder,
),
right: const Text('Not Today')
right: const Text('Not Today'),
),
);
}
@ -190,7 +190,7 @@ void main() {
final IndexedWidgetBuilder itemBuilder = (BuildContext context, int index) {
callbackTracker.add(index);
return new Text('$index', key: new ValueKey<int>(index));
return new Text('$index', key: new ValueKey<int>(index), textDirection: TextDirection.ltr);
};
final Widget testWidget = new Directionality(

View File

@ -2,10 +2,10 @@
// 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/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
const List<int> items = const <int>[0, 1, 2, 3, 4, 5];

Some files were not shown because too many files have changed in this diff Show More