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'; 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'; 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 double devicePixelRatio = ui.window.devicePixelRatio;
final ui.Size logicalSize = ui.window.physicalSize / 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.'); ..addText('Hello, world.');
final ui.Paragraph paragraph = paragraphBuilder.build() final ui.Paragraph paragraph = paragraphBuilder.build()
..layout(new ui.ParagraphConstraints(width: logicalSize.width)); ..layout(new ui.ParagraphConstraints(width: logicalSize.width));

View File

@ -52,7 +52,13 @@ void beginFrame(Duration timeStamp) {
void main() { void main() {
// To create a paragraph of text, we use ParagraphBuilder. // 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. // We first push a style that turns the text blue.
..pushStyle(new ui.TextStyle(color: const ui.Color(0xFF0000FF))) ..pushStyle(new ui.TextStyle(color: const ui.Color(0xFF0000FF)))
..addText('Hello, ') ..addText('Hello, ')

View File

@ -14,24 +14,36 @@ void main() {
void addAlignmentRow(CrossAxisAlignment crossAxisAlignment) { void addAlignmentRow(CrossAxisAlignment crossAxisAlignment) {
TextStyle style = const TextStyle(color: const Color(0xFF000000)); 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))); table.add(new RenderPadding(child: paragraph, padding: const EdgeInsets.only(top: 20.0)));
final RenderFlex row = new RenderFlex(crossAxisAlignment: crossAxisAlignment, textBaseline: TextBaseline.alphabetic); final RenderFlex row = new RenderFlex(crossAxisAlignment: crossAxisAlignment, textBaseline: TextBaseline.alphabetic);
style = const TextStyle(fontSize: 15.0, color: const Color(0xFF000000)); style = const TextStyle(fontSize: 15.0, color: const Color(0xFF000000));
row.add(new RenderDecoratedBox( row.add(new RenderDecoratedBox(
decoration: const BoxDecoration(color: const Color(0x7FFFCCCC)), 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)); style = const TextStyle(fontSize: 10.0, color: const Color(0xFF000000));
row.add(new RenderDecoratedBox( row.add(new RenderDecoratedBox(
decoration: const BoxDecoration(color: const Color(0x7FCCFFCC)), 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); final RenderFlex subrow = new RenderFlex(crossAxisAlignment: crossAxisAlignment, textBaseline: TextBaseline.alphabetic);
style = const TextStyle(fontSize: 25.0, color: const Color(0xFF000000)); style = const TextStyle(fontSize: 25.0, color: const Color(0xFF000000));
subrow.add(new RenderDecoratedBox( subrow.add(new RenderDecoratedBox(
decoration: const BoxDecoration(color: const Color(0x7FCCCCFF)), 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))); subrow.add(new RenderSolidColorBox(const Color(0x7FCCFFFF), desiredSize: const Size(30.0, 40.0)));
row.add(subrow); row.add(subrow);
@ -48,7 +60,10 @@ void main() {
void addJustificationRow(MainAxisAlignment justify) { void addJustificationRow(MainAxisAlignment justify) {
const TextStyle style = const TextStyle(color: const Color(0xFF000000)); 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))); table.add(new RenderPadding(child: paragraph, padding: const EdgeInsets.only(top: 20.0)));
final RenderFlex row = new RenderFlex(direction: Axis.horizontal); final RenderFlex row = new RenderFlex(direction: Axis.horizontal);
row.add(new RenderSolidColorBox(const Color(0xFFFFCCCC), desiredSize: const Size(80.0, 60.0))); 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, alignment: FractionalOffset.center,
// We use a RenderParagraph to display the text 'Hello, world.' without // We use a RenderParagraph to display the text 'Hello, world.' without
// any explicit styling. // 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( final RenderParagraph paragraph = new RenderParagraph(
const TextSpan( const TextSpan(
style: const TextStyle(color: Colors.black87), 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. // 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 // The bottom later is our RenderDots object, and on top of that we show the
@ -114,7 +115,7 @@ void main() {
children: <RenderBox>[ children: <RenderBox>[
new RenderDots(), new RenderDots(),
paragraph, paragraph,
] ],
); );
// The "parentData" field of a render object is controlled by the render // 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 // 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'; 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) { void attachWidgetTreeToRenderTree(RenderProxyBox container) {
element = new RenderObjectToWidgetAdapter<RenderBox>( element = new RenderObjectToWidgetAdapter<RenderBox>(
container: container, container: container,
child: new Directionality(
textDirection: TextDirection.ltr,
child: new Container( child: new Container(
height: 300.0, height: 300.0,
child: new Column( child: new Column(
@ -53,24 +55,25 @@ void attachWidgetTreeToRenderTree(RenderProxyBox container) {
children: <Widget>[ children: <Widget>[
new Image.network('https://flutter.io/images/favicon.png'), new Image.network('https://flutter.io/images/favicon.png'),
const Text('PRESS ME'), const Text('PRESS ME'),
] ],
), ),
onPressed: () { onPressed: () {
value = value == null ? 0.1 : (value + 0.1) % 1.0; value = value == null ? 0.1 : (value + 0.1) % 1.0;
attachWidgetTreeToRenderTree(container); attachWidgetTreeToRenderTree(container);
} },
), ),
new CircularProgressIndicator(value: value), new CircularProgressIndicator(value: value),
], ],
mainAxisAlignment: MainAxisAlignment.spaceAround mainAxisAlignment: MainAxisAlignment.spaceAround,
) ),
) ),
), ),
const Rectangle(const Color(0xFFFFFF00)), const Rectangle(const Color(0xFFFFFF00)),
], ],
mainAxisAlignment: MainAxisAlignment.spaceBetween mainAxisAlignment: MainAxisAlignment.spaceBetween,
) ),
) ),
),
).attachToRenderTree(owner, element); ).attachToRenderTree(owner, element);
} }

View File

@ -46,3 +46,4 @@ export 'src/foundation/print.dart';
export 'src/foundation/profile.dart'; export 'src/foundation/profile.dart';
export 'src/foundation/serialization.dart'; export 'src/foundation/serialization.dart';
export 'src/foundation/synchronous_future.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, @required TextDirection textDirection,
}) : assert(value != null && value >= 0.0 && value <= 1.0), }) : assert(value != null && value >= 0.0 && value <= 1.0),
assert(textDirection != null), assert(textDirection != null),
_label = label,
_value = value, _value = value,
_divisions = divisions, _divisions = divisions,
_activeColor = activeColor, _activeColor = activeColor,
@ -303,7 +304,7 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler {
_textTheme = textTheme, _textTheme = textTheme,
_onChanged = onChanged, _onChanged = onChanged,
_textDirection = textDirection { _textDirection = textDirection {
this.label = label; _updateLabelPainter();
final GestureArenaTeam team = new GestureArenaTeam(); final GestureArenaTeam team = new GestureArenaTeam();
_drag = new HorizontalDragGestureRecognizer() _drag = new HorizontalDragGestureRecognizer()
..team = team ..team = team
@ -356,19 +357,7 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler {
if (value == _label) if (value == _label)
return; return;
_label = value; _label = value;
if (value != null) { _updateLabelPainter();
// 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();
} }
Color get activeColor => _activeColor; Color get activeColor => _activeColor;
@ -424,11 +413,29 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler {
TextDirection _textDirection; TextDirection _textDirection;
set textDirection(TextDirection value) { set textDirection(TextDirection value) {
assert(value != null); assert(value != null);
if (_textDirection == value) if (value == _textDirection)
return; return;
_textDirection = value; _textDirection = value;
// TODO(abarth): Update _labelPainter's text direction. _updateLabelPainter();
markNeedsPaint(); }
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; 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) ..lineTo(center.dx + tipAttachment, center.dy + tipAttachment)
..close(); ..close();
canvas.drawPath(path, primaryPaint); canvas.drawPath(path, primaryPaint);
_labelPainter.layout();
final Offset labelOffset = new Offset( final Offset labelOffset = new Offset(
center.dx - _labelPainter.width / 2.0, center.dx - _labelPainter.width / 2.0,
center.dy - _labelPainter.height / 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 /// 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. /// text field. If it is not null, it must be greater than zero.
/// ///
/// The [keyboardType], [autofocus], [obscureText], and [autocorrect] arguments /// The [keyboardType], [textAlign], [autofocus], [obscureText], and
/// must not be null. /// [autocorrect] arguments must not be null.
const TextField({ const TextField({
Key key, Key key,
this.controller, this.controller,
@ -78,7 +78,7 @@ class TextField extends StatefulWidget {
this.decoration: const InputDecoration(), this.decoration: const InputDecoration(),
this.keyboardType: TextInputType.text, this.keyboardType: TextInputType.text,
this.style, this.style,
this.textAlign, this.textAlign: TextAlign.start,
this.autofocus: false, this.autofocus: false,
this.obscureText: false, this.obscureText: false,
this.autocorrect: true, this.autocorrect: true,
@ -87,6 +87,7 @@ class TextField extends StatefulWidget {
this.onSubmitted, this.onSubmitted,
this.inputFormatters, this.inputFormatters,
}) : assert(keyboardType != null), }) : assert(keyboardType != null),
assert(textAlign != null),
assert(autofocus != null), assert(autofocus != null),
assert(obscureText != null), assert(obscureText != null),
assert(autocorrect != null), assert(autocorrect != null),
@ -125,6 +126,8 @@ class TextField extends StatefulWidget {
final TextStyle style; final TextStyle style;
/// How the text being edited should be aligned horizontally. /// How the text being edited should be aligned horizontally.
///
/// Defaults to [TextAlign.start].
final TextAlign textAlign; final TextAlign textAlign;
/// Whether this text field should focus itself if nothing else is already /// 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. // TODO(abarth): Handle textScaleFactor.
// https://github.com/flutter/flutter/issues/5939 // https://github.com/flutter/flutter/issues/5939
painters[i] = new TextPainter( painters[i] = new TextPainter(
text: new TextSpan(style: style, text: label) text: new TextSpan(style: style, text: label),
textDirection: TextDirection.ltr,
)..layout(); )..layout();
} }
return painters; return painters;

View File

@ -247,9 +247,10 @@ class _FlutterLogoPainter extends BoxPainter {
fontFamily: 'Roboto', 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 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, fontWeight: FontWeight.w300,
textBaseline: TextBaseline.alphabetic textBaseline: TextBaseline.alphabetic,
) ),
) ),
textDirection: TextDirection.ltr,
); );
_textPainter.layout(); _textPainter.layout();
final ui.TextBox textSize = _textPainter.getBoxesForSelection(const TextSelection(baseOffset: 0, extentOffset: kLabel.length)).single; 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 { class TextPainter {
/// Creates a text painter that paints the given text. /// Creates a text painter that paints the given text.
/// ///
/// The text argument is optional but [text] must be non-null before calling /// The `text` and `textDirection` arguments are optional but [text] and
/// [layout]. /// [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. /// The [maxLines] property, if non-null, must be greater than zero.
TextPainter({ TextPainter({
TextSpan text, TextSpan text,
TextAlign textAlign, TextAlign textAlign: TextAlign.start,
TextDirection textDirection,
double textScaleFactor: 1.0, double textScaleFactor: 1.0,
int maxLines, int maxLines,
String ellipsis, String ellipsis,
}) : assert(text == null || text.debugAssertIsValid()), }) : assert(text == null || text.debugAssertIsValid()),
assert(textAlign != null),
assert(textScaleFactor != null), assert(textScaleFactor != null),
assert(maxLines == null || maxLines > 0), assert(maxLines == null || maxLines > 0),
_text = text, _text = text,
_textAlign = textAlign, _textAlign = textAlign,
_textDirection = textDirection,
_textScaleFactor = textScaleFactor, _textScaleFactor = textScaleFactor,
_maxLines = maxLines, _maxLines = maxLines,
_ellipsis = ellipsis; _ellipsis = ellipsis;
@ -58,6 +63,8 @@ class TextPainter {
/// The (potentially styled) text to paint. /// The (potentially styled) text to paint.
/// ///
/// After this is set, you must call [layout] before the next call 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 get text => _text;
TextSpan _text; TextSpan _text;
set text(TextSpan value) { set text(TextSpan value) {
@ -74,9 +81,12 @@ class TextPainter {
/// How the text should be aligned horizontally. /// How the text should be aligned horizontally.
/// ///
/// After this is set, you must call [layout] before the next call to [paint]. /// 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 get textAlign => _textAlign;
TextAlign _textAlign; TextAlign _textAlign;
set textAlign(TextAlign value) { set textAlign(TextAlign value) {
assert(value != null);
if (_textAlign == value) if (_textAlign == value)
return; return;
_textAlign = value; _textAlign = value;
@ -84,6 +94,32 @@ class TextPainter {
_needsLayout = true; _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. /// 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 /// 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.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( return _text.style?.getParagraphStyle(
textAlign: textAlign, textAlign: textAlign,
textDirection: textDirection ?? defaultTextDirection,
textScaleFactor: textScaleFactor, textScaleFactor: textScaleFactor,
maxLines: _maxLines, maxLines: _maxLines,
ellipsis: _ellipsis, ellipsis: _ellipsis,
) ?? new ui.ParagraphStyle( ) ?? new ui.ParagraphStyle(
textAlign: textAlign, textAlign: textAlign,
textDirection: textDirection ?? defaultTextDirection,
maxLines: maxLines, maxLines: maxLines,
ellipsis: ellipsis, ellipsis: ellipsis,
); );
@ -169,11 +211,17 @@ class TextPainter {
/// relative a typical line of text. /// relative a typical line of text.
/// ///
/// Obtaining this value does not require calling [layout]. /// 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 { double get preferredLineHeight {
assert(text != null);
if (_layoutTemplate == null) { if (_layoutTemplate == null) {
final ui.ParagraphBuilder builder = new ui.ParagraphBuilder(_createParagraphStyle()); final ui.ParagraphBuilder builder = new ui.ParagraphBuilder(
if (text.style != null) _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.pushStyle(text.style.getTextStyle(textScaleFactor: textScaleFactor));
builder.addText(_kZeroWidthSpace); builder.addText(_kZeroWidthSpace);
_layoutTemplate = builder.build() _layoutTemplate = builder.build()
@ -272,10 +320,14 @@ class TextPainter {
/// Computes the visual position of the glyphs for painting the text. /// 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 /// 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 /// width as possible while still being greater than or equal to `minWidth` and
/// less than or equal to maxWidth. /// 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 }) { 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) if (!_needsLayout && minWidth == _lastMinWidth && maxWidth == _lastMaxWidth)
return; return;
_needsLayout = false; _needsLayout = false;
@ -352,15 +404,34 @@ class TextPainter {
} }
Offset get _emptyOffset { Offset get _emptyOffset {
// TODO(abarth): Handle the directionality of the text painter itself. assert(!_needsLayout); // implies textDirection is non-null
switch (textAlign ?? TextAlign.left) { assert(textAlign != null);
switch (textAlign) {
case TextAlign.left: case TextAlign.left:
case TextAlign.justify:
return Offset.zero; return Offset.zero;
case TextAlign.right: case TextAlign.right:
return new Offset(width, 0.0); return new Offset(width, 0.0);
case TextAlign.center: case TextAlign.center:
return new Offset(width / 2.0, 0.0); 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; return null;
} }

View File

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

View File

@ -87,6 +87,10 @@ class TextSelectionPoint {
class RenderEditable extends RenderBox { class RenderEditable extends RenderBox {
/// Creates a render object that implements the visual aspects of a text field. /// 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. /// 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 /// 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. /// ViewportOffset.zero] if you have no need for scrolling.
RenderEditable({ RenderEditable({
TextSpan text, TextSpan text,
TextAlign textAlign, @required TextDirection textDirection,
TextAlign textAlign: TextAlign.start,
Color cursorColor, Color cursorColor,
ValueNotifier<bool> showCursor, ValueNotifier<bool> showCursor,
int maxLines: 1, int maxLines: 1,
@ -107,10 +112,17 @@ class RenderEditable extends RenderBox {
@required ViewportOffset offset, @required ViewportOffset offset,
this.onSelectionChanged, this.onSelectionChanged,
this.onCaretChanged, 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(textScaleFactor != null),
assert(offset != 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, _cursorColor = cursorColor,
_showCursor = showCursor ?? new ValueNotifier<bool>(false), _showCursor = showCursor ?? new ValueNotifier<bool>(false),
_maxLines = maxLines, _maxLines = maxLines,
@ -146,7 +158,7 @@ class RenderEditable extends RenderBox {
markNeedsLayout(); markNeedsLayout();
} }
/// The text to display /// The text to display.
TextSpan get text => _textPainter.text; TextSpan get text => _textPainter.text;
final TextPainter _textPainter; final TextPainter _textPainter;
set text(TextSpan value) { set text(TextSpan value) {
@ -157,14 +169,39 @@ class RenderEditable extends RenderBox {
} }
/// How the text should be aligned horizontally. /// How the text should be aligned horizontally.
///
/// This must not be null.
TextAlign get textAlign => _textPainter.textAlign; TextAlign get textAlign => _textPainter.textAlign;
set textAlign(TextAlign value) { set textAlign(TextAlign value) {
assert(value != null);
if (_textPainter.textAlign == value) if (_textPainter.textAlign == value)
return; return;
_textPainter.textAlign = value; _textPainter.textAlign = value;
markNeedsPaint(); 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. /// The color to use when painting the cursor.
Color get cursorColor => _cursorColor; Color get cursorColor => _cursorColor;
Color _cursorColor; Color _cursorColor;

View File

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

View File

@ -2867,11 +2867,11 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder description) { void debugFillProperties(DiagnosticPropertiesBuilder description) {
description.add(new DiagnosticsProperty<dynamic>('creator', debugCreator, defaultValue: null)); 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<ParentData>('parentData', parentData, tooltip: _debugCanParentUseSize == true ? 'can use size' : null, ifNull: 'MISSING'));
description.add(new DiagnosticsProperty<Constraints>('constraints', constraints)); 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 // 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<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( description.add(new FlagProperty(
'isBlockingSemanticsOfPreviouslyPaintedNodes', 'isBlockingSemanticsOfPreviouslyPaintedNodes',
value: isBlockingSemanticsOfPreviouslyPaintedNodes, value: isBlockingSemanticsOfPreviouslyPaintedNodes,

View File

@ -32,19 +32,22 @@ const String _kEllipsis = '\u2026';
class RenderParagraph extends RenderBox { class RenderParagraph extends RenderBox {
/// Creates a paragraph render object. /// Creates a paragraph render object.
/// ///
/// The [text], [overflow], [softWrap], and [textScaleFactor] arguments must /// The [text], [textAlign], [textDirection], [overflow], [softWrap], and
/// not be null. /// [textScaleFactor] arguments must not be null.
/// ///
/// The [maxLines] property may be null (and indeed defaults to null), but if /// The [maxLines] property may be null (and indeed defaults to null), but if
/// it is not null, it must be greater than zero. /// it is not null, it must be greater than zero.
RenderParagraph(TextSpan text, { RenderParagraph(TextSpan text, {
TextAlign textAlign, TextAlign textAlign: TextAlign.start,
@required TextDirection textDirection,
bool softWrap: true, bool softWrap: true,
TextOverflow overflow: TextOverflow.clip, TextOverflow overflow: TextOverflow.clip,
double textScaleFactor: 1.0, double textScaleFactor: 1.0,
int maxLines, int maxLines,
}) : assert(text != null), }) : assert(text != null),
assert(text.debugAssertIsValid()), assert(text.debugAssertIsValid()),
assert(textAlign != null),
assert(textDirection != null),
assert(softWrap != null), assert(softWrap != null),
assert(overflow != null), assert(overflow != null),
assert(textScaleFactor != null), assert(textScaleFactor != null),
@ -54,6 +57,7 @@ class RenderParagraph extends RenderBox {
_textPainter = new TextPainter( _textPainter = new TextPainter(
text: text, text: text,
textAlign: textAlign, textAlign: textAlign,
textDirection: textDirection,
textScaleFactor: textScaleFactor, textScaleFactor: textScaleFactor,
maxLines: maxLines, maxLines: maxLines,
ellipsis: overflow == TextOverflow.ellipsis ? _kEllipsis : null, ellipsis: overflow == TextOverflow.ellipsis ? _kEllipsis : null,
@ -84,12 +88,35 @@ class RenderParagraph extends RenderBox {
/// How the text should be aligned horizontally. /// How the text should be aligned horizontally.
TextAlign get textAlign => _textPainter.textAlign; TextAlign get textAlign => _textPainter.textAlign;
set textAlign(TextAlign value) { set textAlign(TextAlign value) {
assert(value != null);
if (_textPainter.textAlign == value) if (_textPainter.textAlign == value)
return; return;
_textPainter.textAlign = value; _textPainter.textAlign = value;
markNeedsPaint(); 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. /// Whether the text should break at soft line breaks.
/// ///
/// If false, the glyphs in the text will be positioned as if there was /// 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 }) { void _layoutText({ double minWidth: 0.0, double maxWidth: double.INFINITY }) {
_textPainter.layout( final bool widthMatters = softWrap || overflow == TextOverflow.ellipsis;
minWidth: minWidth, _textPainter.layout(minWidth: minWidth, maxWidth: widthMatters ? maxWidth : double.INFINITY);
maxWidth: _softWrap || _overflow == TextOverflow.ellipsis ? maxWidth : double.INFINITY
);
} }
void _layoutTextWithConstraints(BoxConstraints constraints) { void _layoutTextWithConstraints(BoxConstraints constraints) {
@ -245,14 +270,24 @@ class RenderParagraph extends RenderBox {
_overflowShader = null; _overflowShader = null;
break; break;
case TextOverflow.fade: case TextOverflow.fade:
assert(textDirection != null);
final TextPainter fadeSizePainter = new TextPainter( final TextPainter fadeSizePainter = new TextPainter(
text: new TextSpan(style: _textPainter.text.style, text: '\u2026'), text: new TextSpan(style: _textPainter.text.style, text: '\u2026'),
textScaleFactor: textScaleFactor textDirection: textDirection,
textScaleFactor: textScaleFactor,
)..layout(); )..layout();
if (didOverflowWidth) { if (didOverflowWidth) {
final double fadeEnd = size.width; double fadeEnd, fadeStart;
final double fadeStart = fadeEnd - fadeSizePainter.width; switch (textDirection) {
// TODO(abarth): This shader has an LTR bias. 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( _overflowShader = new ui.Gradient.linear(
new Offset(fadeStart, 0.0), new Offset(fadeStart, 0.0),
new Offset(fadeEnd, 0.0), new Offset(fadeEnd, 0.0),
@ -373,10 +408,22 @@ class RenderParagraph extends RenderBox {
void _annotate(SemanticsNode node) { void _annotate(SemanticsNode node) {
node.label = text.toPlainText(); node.label = text.toPlainText();
node.textDirection = textDirection;
} }
@override @override
List<DiagnosticsNode> debugDescribeChildren() { List<DiagnosticsNode> debugDescribeChildren() {
return <DiagnosticsNode>[text.toDiagnosticsNode(name: 'text', style: DiagnosticsTreeStyle.transition)]; 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. /// Creates a render object that attaches a semantic annotation.
/// ///
/// The [container] argument must not be null. /// The [container] argument must not be null.
///
/// If the [label] is not null, the [textDirection] must also not be null.
RenderSemanticsAnnotations({ RenderSemanticsAnnotations({
RenderBox child, RenderBox child,
bool container: false, bool container: false,
bool checked, bool checked,
bool selected, bool selected,
String label, String label,
TextDirection textDirection,
}) : assert(container != null), }) : assert(container != null),
_container = container, _container = container,
_checked = checked, _checked = checked,
_selected = selected, _selected = selected,
_label = label, _label = label,
_textDirection = textDirection,
super(child); super(child);
/// If 'container' is true, this RenderObject will introduce a new /// If 'container' is true, this RenderObject will introduce a new
@ -3182,11 +3186,24 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
markNeedsSemanticsUpdate(onlyLocalUpdates: (value != null) == hadValue); 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 @override
bool get isSemanticBoundary => container; bool get isSemanticBoundary => container;
@override @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) { void _annotate(SemanticsNode node) {
if (checked != null) { if (checked != null) {
@ -3198,6 +3215,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
node.isSelected = selected; node.isSelected = selected;
if (label != null) if (label != null)
node.label = label; node.label = label;
if (textDirection != null)
node.textDirection = textDirection;
} }
} }

View File

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

View File

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

View File

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

View File

@ -3704,20 +3704,25 @@ class Flow extends MultiChildRenderObjectWidget {
class RichText extends LeafRenderObjectWidget { class RichText extends LeafRenderObjectWidget {
/// Creates a paragraph of rich text. /// Creates a paragraph of rich text.
/// ///
/// The [text], [softWrap], [overflow], nad [textScaleFactor] arguments must /// The [text], [textAlign], [softWrap], [overflow], nad [textScaleFactor]
/// not be null. /// arguments must not be null.
/// ///
/// The [maxLines] property may be null (and indeed defaults to null), but if /// The [maxLines] property may be null (and indeed defaults to null), but if
/// it is not null, it must be greater than zero. /// 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({ const RichText({
Key key, Key key,
@required this.text, @required this.text,
this.textAlign, this.textAlign: TextAlign.start,
this.textDirection,
this.softWrap: true, this.softWrap: true,
this.overflow: TextOverflow.clip, this.overflow: TextOverflow.clip,
this.textScaleFactor: 1.0, this.textScaleFactor: 1.0,
this.maxLines, this.maxLines,
}) : assert(text != null), }) : assert(text != null),
assert(textAlign != null),
assert(softWrap != null), assert(softWrap != null),
assert(overflow != null), assert(overflow != null),
assert(textScaleFactor != null), assert(textScaleFactor != null),
@ -3730,6 +3735,22 @@ class RichText extends LeafRenderObjectWidget {
/// How the text should be aligned horizontally. /// How the text should be aligned horizontally.
final TextAlign textAlign; 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. /// 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. /// 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 @override
RenderParagraph createRenderObject(BuildContext context) { 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, return new RenderParagraph(text,
textAlign: textAlign, textAlign: textAlign,
textDirection: direction,
softWrap: softWrap, softWrap: softWrap,
overflow: overflow, overflow: overflow,
textScaleFactor: textScaleFactor, textScaleFactor: textScaleFactor,
@ -3768,6 +3792,7 @@ class RichText extends LeafRenderObjectWidget {
renderObject renderObject
..text = text ..text = text
..textAlign = textAlign ..textAlign = textAlign
..textDirection = textDirection ?? Directionality.of(context)
..softWrap = softWrap ..softWrap = softWrap
..overflow = overflow ..overflow = overflow
..textScaleFactor = textScaleFactor ..textScaleFactor = textScaleFactor
@ -4289,6 +4314,7 @@ class Semantics extends SingleChildRenderObjectWidget {
this.checked, this.checked,
this.selected, this.selected,
this.label, this.label,
this.textDirection,
}) : assert(container != null), }) : assert(container != null),
super(key: key, child: child); super(key: key, child: child);
@ -4317,8 +4343,20 @@ class Semantics extends SingleChildRenderObjectWidget {
final bool selected; final bool selected;
/// Provides a textual description of the widget. /// 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; 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 @override
RenderSemanticsAnnotations createRenderObject(BuildContext context) { RenderSemanticsAnnotations createRenderObject(BuildContext context) {
return new RenderSemanticsAnnotations( return new RenderSemanticsAnnotations(
@ -4326,6 +4364,7 @@ class Semantics extends SingleChildRenderObjectWidget {
checked: checked, checked: checked,
selected: selected, selected: selected,
label: label, label: label,
textDirection: _getTextDirection(context),
); );
} }
@ -4335,7 +4374,8 @@ class Semantics extends SingleChildRenderObjectWidget {
..container = container ..container = container
..checked = checked ..checked = checked
..selected = selected ..selected = selected
..label = label; ..label = label
..textDirection = _getTextDirection(context);
} }
@override @override
@ -4344,7 +4384,8 @@ class Semantics extends SingleChildRenderObjectWidget {
description.add(new DiagnosticsProperty<bool>('container', container)); description.add(new DiagnosticsProperty<bool>('container', container));
description.add(new DiagnosticsProperty<bool>('checked', checked, defaultValue: null)); description.add(new DiagnosticsProperty<bool>('checked', checked, defaultValue: null));
description.add(new DiagnosticsProperty<bool>('selected', selected, 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 /// 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. /// text field. If it is not null, it must be greater than zero.
/// ///
/// The [controller], [focusNode], [style], and [cursorColor] arguments must /// The [controller], [focusNode], [style], [cursorColor], and [textAlign]
/// not be null. /// arguments must not be null.
EditableText({ EditableText({
Key key, Key key,
@required this.controller, @required this.controller,
@ -154,7 +154,8 @@ class EditableText extends StatefulWidget {
this.autocorrect: true, this.autocorrect: true,
@required this.style, @required this.style,
@required this.cursorColor, @required this.cursorColor,
this.textAlign, this.textAlign: TextAlign.start,
this.textDirection,
this.textScaleFactor, this.textScaleFactor,
this.maxLines: 1, this.maxLines: 1,
this.autofocus: false, this.autofocus: false,
@ -171,6 +172,7 @@ class EditableText extends StatefulWidget {
assert(autocorrect != null), assert(autocorrect != null),
assert(style != null), assert(style != null),
assert(cursorColor != null), assert(cursorColor != null),
assert(textAlign != null),
assert(maxLines == null || maxLines > 0), assert(maxLines == null || maxLines > 0),
assert(autofocus != null), assert(autofocus != null),
inputFormatters = maxLines == 1 inputFormatters = maxLines == 1
@ -201,8 +203,25 @@ class EditableText extends StatefulWidget {
final TextStyle style; final TextStyle style;
/// How the text should be aligned horizontally. /// How the text should be aligned horizontally.
///
/// Defaults to [TextAlign.start].
final TextAlign textAlign; 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. /// 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 /// 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)); description.add(new DiagnosticsProperty<bool>('autocorrect', autocorrect, defaultValue: true));
style?.debugFillProperties(description); style?.debugFillProperties(description);
description.add(new EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: null)); 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 DoubleProperty('textScaleFactor', textScaleFactor, defaultValue: null));
description.add(new IntProperty('maxLines', maxLines, defaultValue: 1)); description.add(new IntProperty('maxLines', maxLines, defaultValue: 1));
description.add(new DiagnosticsProperty<bool>('autofocus', autofocus, defaultValue: false)); description.add(new DiagnosticsProperty<bool>('autofocus', autofocus, defaultValue: false));
@ -575,6 +595,12 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
updateKeepAlive(); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
FocusScope.of(context).reparentIfNeeded(widget.focusNode); FocusScope.of(context).reparentIfNeeded(widget.focusNode);
@ -595,6 +621,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
selectionColor: widget.selectionColor, selectionColor: widget.selectionColor,
textScaleFactor: widget.textScaleFactor ?? MediaQuery.of(context, nullOk: true)?.textScaleFactor ?? 1.0, textScaleFactor: widget.textScaleFactor ?? MediaQuery.of(context, nullOk: true)?.textScaleFactor ?? 1.0,
textAlign: widget.textAlign, textAlign: widget.textAlign,
textDirection: _textDirection,
obscureText: widget.obscureText, obscureText: widget.obscureText,
obscureShowCharacterAtIndex: _obscureShowCharTicksPending > 0 ? _obscureLatestCharIndex : null, obscureShowCharacterAtIndex: _obscureShowCharTicksPending > 0 ? _obscureLatestCharIndex : null,
autocorrect: widget.autocorrect, autocorrect: widget.autocorrect,
@ -619,13 +646,15 @@ class _Editable extends LeafRenderObjectWidget {
this.selectionColor, this.selectionColor,
this.textScaleFactor, this.textScaleFactor,
this.textAlign, this.textAlign,
@required this.textDirection,
this.obscureText, this.obscureText,
this.obscureShowCharacterAtIndex, this.obscureShowCharacterAtIndex,
this.autocorrect, this.autocorrect,
this.offset, this.offset,
this.onSelectionChanged, this.onSelectionChanged,
this.onCaretChanged, this.onCaretChanged,
}) : super(key: key); }) : assert(textDirection != null),
super(key: key);
final TextEditingValue value; final TextEditingValue value;
final TextStyle style; final TextStyle style;
@ -635,6 +664,7 @@ class _Editable extends LeafRenderObjectWidget {
final Color selectionColor; final Color selectionColor;
final double textScaleFactor; final double textScaleFactor;
final TextAlign textAlign; final TextAlign textAlign;
final TextDirection textDirection;
final bool obscureText; final bool obscureText;
final int obscureShowCharacterAtIndex; final int obscureShowCharacterAtIndex;
final bool autocorrect; final bool autocorrect;
@ -652,6 +682,7 @@ class _Editable extends LeafRenderObjectWidget {
selectionColor: selectionColor, selectionColor: selectionColor,
textScaleFactor: textScaleFactor, textScaleFactor: textScaleFactor,
textAlign: textAlign, textAlign: textAlign,
textDirection: textDirection,
selection: value.selection, selection: value.selection,
offset: offset, offset: offset,
onSelectionChanged: onSelectionChanged, onSelectionChanged: onSelectionChanged,
@ -669,6 +700,7 @@ class _Editable extends LeafRenderObjectWidget {
..selectionColor = selectionColor ..selectionColor = selectionColor
..textScaleFactor = textScaleFactor ..textScaleFactor = textScaleFactor
..textAlign = textAlign ..textAlign = textAlign
..textDirection = textDirection
..selection = value.selection ..selection = value.selection
..offset = offset ..offset = offset
..onSelectionChanged = onSelectionChanged ..onSelectionChanged = onSelectionChanged

View File

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

View File

@ -228,15 +228,29 @@ String _getMessage(SemanticsNode node) {
if (isAdjustable) if (isAdjustable)
annotations.add('adjustable'); annotations.add('adjustable');
String message;
if (annotations.isEmpty) {
assert(data.label != null); assert(data.label != null);
message = data.label; String message;
} else {
if (data.label.isEmpty) { if (data.label.isEmpty) {
message = annotations.join('; '); message = annotations.join('; ');
} else { } 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.save();
canvas.clipRect(rect); canvas.clipRect(rect);
final TextPainter textPainter = new TextPainter() 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 ..textAlign = TextAlign.center
..layout(maxWidth: rect.width); ..layout(maxWidth: rect.width);

View File

@ -200,6 +200,7 @@ class Text extends StatelessWidget {
Key key, Key key,
this.style, this.style,
this.textAlign, this.textAlign,
this.textDirection,
this.softWrap, this.softWrap,
this.overflow, this.overflow,
this.textScaleFactor, this.textScaleFactor,
@ -220,6 +221,21 @@ class Text extends StatelessWidget {
/// How the text should be aligned horizontally. /// How the text should be aligned horizontally.
final TextAlign textAlign; 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. /// 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. /// 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) if (style == null || style.inherit)
effectiveTextStyle = defaultTextStyle.style.merge(style); effectiveTextStyle = defaultTextStyle.style.merge(style);
return new RichText( 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, softWrap: softWrap ?? defaultTextStyle.softWrap,
overflow: overflow ?? defaultTextStyle.overflow, overflow: overflow ?? defaultTextStyle.overflow,
textScaleFactor: textScaleFactor ?? MediaQuery.of(context, nullOk: true)?.textScaleFactor ?? 1.0, textScaleFactor: textScaleFactor ?? MediaQuery.of(context, nullOk: true)?.textScaleFactor ?? 1.0,

View File

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

View File

@ -14,7 +14,7 @@ const TextStyle testStyle = const TextStyle(
void main() { void main() {
testWidgets('Default layout minimum size', (WidgetTester tester) async { testWidgets('Default layout minimum size', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
const Center(child: const CupertinoButton( boilerplate(child: const CupertinoButton(
child: const Text('X', style: testStyle), child: const Text('X', style: testStyle),
onPressed: null, onPressed: null,
)) ))
@ -30,7 +30,7 @@ void main() {
testWidgets('Minimum size parameter', (WidgetTester tester) async { testWidgets('Minimum size parameter', (WidgetTester tester) async {
final double minSize = 60.0; final double minSize = 60.0;
await tester.pumpWidget( await tester.pumpWidget(
new Center(child: new CupertinoButton( boilerplate(child: new CupertinoButton(
child: const Text('X', style: testStyle), child: const Text('X', style: testStyle),
onPressed: null, onPressed: null,
minSize: minSize, minSize: minSize,
@ -46,7 +46,7 @@ void main() {
testWidgets('Size grows with text', (WidgetTester tester) async { testWidgets('Size grows with text', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
const Center(child: const CupertinoButton( boilerplate(child: const CupertinoButton(
child: const Text('XXXX', style: testStyle), child: const Text('XXXX', style: testStyle),
onPressed: null, onPressed: null,
)) ))
@ -60,7 +60,7 @@ void main() {
}); });
testWidgets('Button with background is wider', (WidgetTester tester) async { 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), child: const Text('X', style: testStyle),
onPressed: null, onPressed: null,
color: const Color(0xFFFFFFFF), color: const Color(0xFFFFFFFF),
@ -74,7 +74,7 @@ void main() {
}); });
testWidgets('Custom padding', (WidgetTester tester) async { 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), child: const Text(' ', style: testStyle),
onPressed: null, onPressed: null,
padding: const EdgeInsets.all(100.0), padding: const EdgeInsets.all(100.0),
@ -91,7 +91,7 @@ void main() {
await tester.pumpWidget( await tester.pumpWidget(
new StatefulBuilder( new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) { builder: (BuildContext context, StateSetter setState) {
return new Center( return boilerplate(
child: new CupertinoButton( child: new CupertinoButton(
child: const Text('Tap me'), child: const Text('Tap me'),
onPressed: () { onPressed: () {
@ -115,7 +115,7 @@ void main() {
}); });
testWidgets('Disabled button doesn\'t animate', (WidgetTester tester) async { 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'), child: const Text('Tap me'),
onPressed: null, onPressed: null,
))); )));
@ -126,7 +126,7 @@ void main() {
}); });
testWidgets('pressedOpacity defaults to 0.1', (WidgetTester tester) async { 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'), child: const Text('Tap me'),
onPressed: () { }, onPressed: () { },
))); )));
@ -146,7 +146,7 @@ void main() {
testWidgets('pressedOpacity parameter', (WidgetTester tester) async { testWidgets('pressedOpacity parameter', (WidgetTester tester) async {
final double pressedOpacity = 0.5; final double pressedOpacity = 0.5;
await tester.pumpWidget(new Center(child: new CupertinoButton( await tester.pumpWidget(boilerplate(child: new CupertinoButton(
pressedOpacity: pressedOpacity, pressedOpacity: pressedOpacity,
child: const Text('Tap me'), child: const Text('Tap me'),
onPressed: () { }, onPressed: () { },
@ -165,3 +165,10 @@ void main() {
expect(opacity.opacity, pressedOpacity); 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 { testWidgets('Dialog destructive action styles', (WidgetTester tester) async {
await tester.pumpWidget(const CupertinoDialogAction( await tester.pumpWidget(boilerplate(const CupertinoDialogAction(
isDestructiveAction: true, isDestructiveAction: true,
child: const Text('Ok'), child: const Text('Ok'),
)); )));
final DefaultTextStyle widget = tester.widget(find.byType(DefaultTextStyle)); final DefaultTextStyle widget = tester.widget(find.byType(DefaultTextStyle));
@ -76,10 +76,10 @@ void main() {
}); });
testWidgets('Dialog default action styles', (WidgetTester tester) async { testWidgets('Dialog default action styles', (WidgetTester tester) async {
await tester.pumpWidget(const CupertinoDialogAction( await tester.pumpWidget(boilerplate(const CupertinoDialogAction(
isDefaultAction: true, isDefaultAction: true,
child: const Text('Ok'), child: const Text('Ok'),
)); )));
final DefaultTextStyle widget = tester.widget(find.byType(DefaultTextStyle)); final DefaultTextStyle widget = tester.widget(find.byType(DefaultTextStyle));
@ -87,11 +87,11 @@ void main() {
}); });
testWidgets('Default and destructive style', (WidgetTester tester) async { testWidgets('Default and destructive style', (WidgetTester tester) async {
await tester.pumpWidget(const CupertinoDialogAction( await tester.pumpWidget(boilerplate(const CupertinoDialogAction(
isDefaultAction: true, isDefaultAction: true,
isDestructiveAction: true, isDestructiveAction: true,
child: const Text('Ok'), child: const Text('Ok'),
)); )));
final DefaultTextStyle widget = tester.widget(find.byType(DefaultTextStyle)); final DefaultTextStyle widget = tester.widget(find.byType(DefaultTextStyle));
@ -99,3 +99,10 @@ void main() {
expect(widget.style.color.red, greaterThan(widget.style.color.blue)); 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 { testWidgets('Does FlatButton contribute semantics', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester); final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget( await tester.pumpWidget(
new Material( new Directionality(
textDirection: TextDirection.ltr,
child: new Material(
child: new Center( child: new Center(
child: new FlatButton( child: new FlatButton(
onPressed: () { }, onPressed: () { },
child: const Text('ABC') child: const Text('ABC')
) ),
) ),
) ),
),
); );
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
@ -58,10 +61,13 @@ void main() {
); );
await tester.pumpWidget( await tester.pumpWidget(
new Theme( new Directionality(
textDirection: TextDirection.ltr,
child: new Theme(
data: new ThemeData(), data: new ThemeData(),
child: buttonWidget, child: buttonWidget,
), ),
),
); );
final Offset center = tester.getCenter(find.byType(MaterialButton)); final Offset center = tester.getCenter(find.byType(MaterialButton));
@ -88,13 +94,16 @@ void main() {
); );
await tester.pumpWidget( await tester.pumpWidget(
new Theme( new Directionality(
textDirection: TextDirection.ltr,
child: new Theme(
data: new ThemeData( data: new ThemeData(
highlightColor: themeHighlightColor1, highlightColor: themeHighlightColor1,
splashColor: themeSplashColor1, splashColor: themeSplashColor1,
), ),
child: buttonWidget, child: buttonWidget,
), ),
),
); );
expect( expect(
@ -108,13 +117,16 @@ void main() {
final Color themeHighlightColor2 = const Color(0xFF002200); final Color themeHighlightColor2 = const Color(0xFF002200);
await tester.pumpWidget( await tester.pumpWidget(
new Theme( new Directionality(
textDirection: TextDirection.ltr,
child: new Theme(
data: new ThemeData( data: new ThemeData(
highlightColor: themeHighlightColor2, highlightColor: themeHighlightColor2,
splashColor: themeSplashColor2, splashColor: themeSplashColor2,
), ),
child: buttonWidget, // same widget, so does not get updated because of us child: buttonWidget, // same widget, so does not get updated because of us
), ),
),
); );
expect( expect(

View File

@ -10,7 +10,7 @@ void main() {
testWidgets('CircleAvatar with background color', (WidgetTester tester) async { testWidgets('CircleAvatar with background color', (WidgetTester tester) async {
final Color backgroundColor = Colors.blue.shade400; final Color backgroundColor = Colors.blue.shade400;
await tester.pumpWidget( await tester.pumpWidget(
new Center( wrap(
child: new CircleAvatar( child: new CircleAvatar(
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
radius: 50.0, radius: 50.0,
@ -33,7 +33,7 @@ void main() {
testWidgets('CircleAvatar with foreground color', (WidgetTester tester) async { testWidgets('CircleAvatar with foreground color', (WidgetTester tester) async {
final Color foregroundColor = Colors.red.shade100; final Color foregroundColor = Colors.red.shade100;
await tester.pumpWidget( await tester.pumpWidget(
new Center( wrap(
child: new CircleAvatar( child: new CircleAvatar(
foregroundColor: foregroundColor, foregroundColor: foregroundColor,
child: const Text('Z'), child: const Text('Z'),
@ -60,9 +60,9 @@ void main() {
primaryColorBrightness: Brightness.light, primaryColorBrightness: Brightness.light,
); );
await tester.pumpWidget( await tester.pumpWidget(
new Theme( wrap(
child: new Theme(
data: theme, data: theme,
child: const Center(
child: const CircleAvatar( child: const CircleAvatar(
child: const Text('Z'), child: const Text('Z'),
), ),
@ -79,3 +79,10 @@ void main() {
expect(paragraph.text.style.color, equals(theme.primaryTextTheme.title.color)); 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; bool expanded = false;
await tester.pumpWidget( await tester.pumpWidget(
new Material( wrap(
child: new Center(
child: new ExpandIcon( child: new ExpandIcon(
onPressed: (bool isExpanded) { onPressed: (bool isExpanded) {
expanded = !expanded; expanded = !expanded;
} }
) )
) )
)
); );
expect(expanded, isFalse); expect(expanded, isFalse);
@ -31,13 +29,11 @@ void main() {
testWidgets('ExpandIcon disabled', (WidgetTester tester) async { testWidgets('ExpandIcon disabled', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
const Material( wrap(
child: const Center(
child: const ExpandIcon( child: const ExpandIcon(
onPressed: null onPressed: null
) )
) )
)
); );
final IconTheme iconTheme = tester.firstWidget(find.byType(IconTheme)); final IconTheme iconTheme = tester.firstWidget(find.byType(IconTheme));
@ -48,8 +44,7 @@ void main() {
bool expanded = false; bool expanded = false;
await tester.pumpWidget( await tester.pumpWidget(
new Material( wrap(
child: new Center(
child: new ExpandIcon( child: new ExpandIcon(
isExpanded: false, isExpanded: false,
onPressed: (bool isExpanded) { onPressed: (bool isExpanded) {
@ -57,12 +52,10 @@ void main() {
} }
) )
) )
)
); );
await tester.pumpWidget( await tester.pumpWidget(
new Material( wrap(
child: new Center(
child: new ExpandIcon( child: new ExpandIcon(
isExpanded: true, isExpanded: true,
onPressed: (bool isExpanded) { onPressed: (bool isExpanded) {
@ -70,9 +63,17 @@ void main() {
} }
) )
) )
)
); );
expect(expanded, isFalse); 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( return new GestureDetector(
onTap: tapHandler(context), onTap: tapHandler(context),
onLongPress: longPressHandler(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'; import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
testWidgets('Floating Action Button control test', testWidgets('Floating Action Button control test', (WidgetTester tester) async {
(WidgetTester tester) async {
bool didPressButton = false; bool didPressButton = false;
await tester.pumpWidget( await tester.pumpWidget(
new Center( new Directionality(
textDirection: TextDirection.ltr,
child: new Center(
child: new FloatingActionButton( child: new FloatingActionButton(
onPressed: () { onPressed: () {
didPressButton = true; didPressButton = true;
@ -18,6 +19,7 @@ void main() {
child: const Icon(Icons.add), child: const Icon(Icons.add),
), ),
), ),
),
); );
expect(didPressButton, isFalse); expect(didPressButton, isFalse);

View File

@ -38,7 +38,12 @@ void main() {
expect(tester.getBottomLeft(find.byKey(headerKey)).dy, expect(tester.getBottomLeft(find.byKey(headerKey)).dy,
lessThan(tester.getTopLeft(find.byKey(footerKey)).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); 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 { testWidgets('test default icon buttons are sized up to 48', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
new Material( wrap(
child: new Center(
child: new IconButton( child: new IconButton(
onPressed: mockOnPressedFunction, onPressed: mockOnPressedFunction,
icon: const Icon(Icons.link), icon: const Icon(Icons.link),
), ),
), ),
),
); );
final RenderBox iconButton = tester.renderObject(find.byType(IconButton)); 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 { testWidgets('test small icons are sized up to 48dp', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
new Material( wrap(
child: new Center(
child: new IconButton( child: new IconButton(
iconSize: 10.0, iconSize: 10.0,
onPressed: mockOnPressedFunction, onPressed: mockOnPressedFunction,
icon: const Icon(Icons.link), icon: const Icon(Icons.link),
), ),
), ),
),
); );
final RenderBox iconButton = tester.renderObject(find.byType(IconButton)); 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 { testWidgets('test icons can be small when total size is >48dp', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
new Material( wrap(
child: new Center(
child: new IconButton( child: new IconButton(
iconSize: 10.0, iconSize: 10.0,
padding: const EdgeInsets.all(30.0), padding: const EdgeInsets.all(30.0),
@ -69,7 +64,6 @@ void main() {
icon: const Icon(Icons.link), icon: const Icon(Icons.link),
), ),
), ),
),
); );
final RenderBox iconButton = tester.renderObject(find.byType(IconButton)); final RenderBox iconButton = tester.renderObject(find.byType(IconButton));
@ -78,8 +72,7 @@ void main() {
testWidgets('test default icon buttons are constrained', (WidgetTester tester) async { testWidgets('test default icon buttons are constrained', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
new Material( wrap(
child: new Center(
child: new IconButton( child: new IconButton(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
onPressed: mockOnPressedFunction, onPressed: mockOnPressedFunction,
@ -87,7 +80,6 @@ void main() {
iconSize: 80.0, iconSize: 80.0,
), ),
), ),
),
); );
final RenderBox box = tester.renderObject(find.byType(IconButton)); final RenderBox box = tester.renderObject(find.byType(IconButton));
@ -120,15 +112,13 @@ void main() {
testWidgets('test default padding', (WidgetTester tester) async { testWidgets('test default padding', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
new Material( wrap(
child: new Center(
child: new IconButton( child: new IconButton(
onPressed: mockOnPressedFunction, onPressed: mockOnPressedFunction,
icon: const Icon(Icons.ac_unit), icon: const Icon(Icons.ac_unit),
iconSize: 80.0, iconSize: 80.0,
), ),
), ),
),
); );
final RenderBox box = tester.renderObject(find.byType(IconButton)); final RenderBox box = tester.renderObject(find.byType(IconButton));
@ -203,15 +193,13 @@ void main() {
final Color directSplashColor = const Color(0xFF00000F); final Color directSplashColor = const Color(0xFF00000F);
final Color directHighlightColor = const Color(0xFF0000F0); final Color directHighlightColor = const Color(0xFF0000F0);
Widget buttonWidget = new Material( Widget buttonWidget = wrap(
child: new Center(
child: new IconButton( child: new IconButton(
icon: const Icon(Icons.android), icon: const Icon(Icons.android),
splashColor: directSplashColor, splashColor: directSplashColor,
highlightColor: directHighlightColor, highlightColor: directHighlightColor,
onPressed: () { /* enable the button */ }, onPressed: () { /* enable the button */ },
), ),
),
); );
await tester.pumpWidget( await tester.pumpWidget(
@ -236,13 +224,11 @@ void main() {
final Color themeSplashColor1 = const Color(0xFF000F00); final Color themeSplashColor1 = const Color(0xFF000F00);
final Color themeHighlightColor1 = const Color(0xFF00FF00); final Color themeHighlightColor1 = const Color(0xFF00FF00);
buttonWidget = new Material( buttonWidget = wrap(
child: new Center(
child: new IconButton( child: new IconButton(
icon: const Icon(Icons.android), icon: const Icon(Icons.android),
onPressed: () { /* enable the button */ }, onPressed: () { /* enable the button */ },
), ),
),
); );
await tester.pumpWidget( await tester.pumpWidget(
@ -285,3 +271,12 @@ void main() {
await gesture.up(); 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 { testWidgets('RefreshIndicator', (WidgetTester tester) async {
refreshCalled = false; refreshCalled = false;
await tester.pumpWidget( await tester.pumpWidget(
new Directionality( new MaterialApp(
textDirection: TextDirection.ltr, home: new RefreshIndicator(
child: new RefreshIndicator(
onRefresh: refresh, onRefresh: refresh,
child: new ListView( child: new ListView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
@ -51,9 +50,8 @@ void main() {
testWidgets('RefreshIndicator - bottom', (WidgetTester tester) async { testWidgets('RefreshIndicator - bottom', (WidgetTester tester) async {
refreshCalled = false; refreshCalled = false;
await tester.pumpWidget( await tester.pumpWidget(
new Directionality( new MaterialApp(
textDirection: TextDirection.ltr, home: new RefreshIndicator(
child: new RefreshIndicator(
onRefresh: refresh, onRefresh: refresh,
child: new ListView( child: new ListView(
reverse: true, reverse: true,
@ -80,9 +78,8 @@ void main() {
testWidgets('RefreshIndicator - top - position', (WidgetTester tester) async { testWidgets('RefreshIndicator - top - position', (WidgetTester tester) async {
refreshCalled = false; refreshCalled = false;
await tester.pumpWidget( await tester.pumpWidget(
new Directionality( new MaterialApp(
textDirection: TextDirection.ltr, home: new RefreshIndicator(
child: new RefreshIndicator(
onRefresh: holdRefresh, onRefresh: holdRefresh,
child: new ListView( child: new ListView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
@ -107,9 +104,8 @@ void main() {
testWidgets('RefreshIndicator - bottom - position', (WidgetTester tester) async { testWidgets('RefreshIndicator - bottom - position', (WidgetTester tester) async {
refreshCalled = false; refreshCalled = false;
await tester.pumpWidget( await tester.pumpWidget(
new Directionality( new MaterialApp(
textDirection: TextDirection.ltr, home: new RefreshIndicator(
child: new RefreshIndicator(
onRefresh: holdRefresh, onRefresh: holdRefresh,
child: new ListView( child: new ListView(
reverse: true, reverse: true,
@ -135,9 +131,8 @@ void main() {
testWidgets('RefreshIndicator - no movement', (WidgetTester tester) async { testWidgets('RefreshIndicator - no movement', (WidgetTester tester) async {
refreshCalled = false; refreshCalled = false;
await tester.pumpWidget( await tester.pumpWidget(
new Directionality( new MaterialApp(
textDirection: TextDirection.ltr, home: new RefreshIndicator(
child: new RefreshIndicator(
onRefresh: refresh, onRefresh: refresh,
child: new ListView( child: new ListView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
@ -164,9 +159,8 @@ void main() {
testWidgets('RefreshIndicator - not enough', (WidgetTester tester) async { testWidgets('RefreshIndicator - not enough', (WidgetTester tester) async {
refreshCalled = false; refreshCalled = false;
await tester.pumpWidget( await tester.pumpWidget(
new Directionality( new MaterialApp(
textDirection: TextDirection.ltr, home: new RefreshIndicator(
child: new RefreshIndicator(
onRefresh: refresh, onRefresh: refresh,
child: new ListView( child: new ListView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
@ -192,9 +186,8 @@ void main() {
testWidgets('RefreshIndicator - show - slow', (WidgetTester tester) async { testWidgets('RefreshIndicator - show - slow', (WidgetTester tester) async {
refreshCalled = false; refreshCalled = false;
await tester.pumpWidget( await tester.pumpWidget(
new Directionality( new MaterialApp(
textDirection: TextDirection.ltr, home: new RefreshIndicator(
child: new RefreshIndicator(
onRefresh: holdRefresh, // this one never returns onRefresh: holdRefresh, // this one never returns
child: new ListView( child: new ListView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
@ -236,9 +229,8 @@ void main() {
testWidgets('RefreshIndicator - show - fast', (WidgetTester tester) async { testWidgets('RefreshIndicator - show - fast', (WidgetTester tester) async {
refreshCalled = false; refreshCalled = false;
await tester.pumpWidget( await tester.pumpWidget(
new Directionality( new MaterialApp(
textDirection: TextDirection.ltr, home: new RefreshIndicator(
child: new RefreshIndicator(
onRefresh: refresh, onRefresh: refresh,
child: new ListView( child: new ListView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
@ -281,9 +273,8 @@ void main() {
testWidgets('RefreshIndicator - show - fast - twice', (WidgetTester tester) async { testWidgets('RefreshIndicator - show - fast - twice', (WidgetTester tester) async {
refreshCalled = false; refreshCalled = false;
await tester.pumpWidget( await tester.pumpWidget(
new Directionality( new MaterialApp(
textDirection: TextDirection.ltr, home: new RefreshIndicator(
child: new RefreshIndicator(
onRefresh: refresh, onRefresh: refresh,
child: new ListView( child: new ListView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),

View File

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

View File

@ -32,7 +32,9 @@ void main() {
testWidgets('Does tooltip end up in the right place - center', (WidgetTester tester) async { testWidgets('Does tooltip end up in the right place - center', (WidgetTester tester) async {
final GlobalKey key = new GlobalKey(); final GlobalKey key = new GlobalKey();
await tester.pumpWidget( await tester.pumpWidget(
new Overlay( new Directionality(
textDirection: TextDirection.ltr,
child: new Overlay(
initialEntries: <OverlayEntry>[ initialEntries: <OverlayEntry>[
new OverlayEntry( new OverlayEntry(
builder: (BuildContext context) { builder: (BuildContext context) {
@ -50,16 +52,17 @@ void main() {
preferBelow: false, preferBelow: false,
child: new Container( child: new Container(
width: 0.0, 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 (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) 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 { testWidgets('Does tooltip end up in the right place - top left', (WidgetTester tester) async {
final GlobalKey key = new GlobalKey(); final GlobalKey key = new GlobalKey();
await tester.pumpWidget( await tester.pumpWidget(
new Overlay( new Directionality(
textDirection: TextDirection.ltr,
child: new Overlay(
initialEntries: <OverlayEntry>[ initialEntries: <OverlayEntry>[
new OverlayEntry( new OverlayEntry(
builder: (BuildContext context) { builder: (BuildContext context) {
@ -103,16 +108,17 @@ void main() {
preferBelow: false, preferBelow: false,
child: new Container( child: new Container(
width: 0.0, 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 (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) 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 { testWidgets('Does tooltip end up in the right place - center prefer above fits', (WidgetTester tester) async {
final GlobalKey key = new GlobalKey(); final GlobalKey key = new GlobalKey();
await tester.pumpWidget( await tester.pumpWidget(
new Overlay( new Directionality(
textDirection: TextDirection.ltr,
child: new Overlay(
initialEntries: <OverlayEntry>[ initialEntries: <OverlayEntry>[
new OverlayEntry( new OverlayEntry(
builder: (BuildContext context) { builder: (BuildContext context) {
@ -152,16 +160,17 @@ void main() {
preferBelow: false, preferBelow: false,
child: new Container( child: new Container(
width: 0.0, 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 (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) 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 { testWidgets('Does tooltip end up in the right place - center prefer above does not fit', (WidgetTester tester) async {
final GlobalKey key = new GlobalKey(); final GlobalKey key = new GlobalKey();
await tester.pumpWidget( await tester.pumpWidget(
new Overlay( new Directionality(
textDirection: TextDirection.ltr,
child: new Overlay(
initialEntries: <OverlayEntry>[ initialEntries: <OverlayEntry>[
new OverlayEntry( new OverlayEntry(
builder: (BuildContext context) { builder: (BuildContext context) {
@ -203,16 +214,17 @@ void main() {
preferBelow: false, preferBelow: false,
child: new Container( child: new Container(
width: 0.0, 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 (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) 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 { testWidgets('Does tooltip end up in the right place - center prefer below fits', (WidgetTester tester) async {
final GlobalKey key = new GlobalKey(); final GlobalKey key = new GlobalKey();
await tester.pumpWidget( await tester.pumpWidget(
new Overlay( new Directionality(
textDirection: TextDirection.ltr,
child: new Overlay(
initialEntries: <OverlayEntry>[ initialEntries: <OverlayEntry>[
new OverlayEntry( new OverlayEntry(
builder: (BuildContext context) { builder: (BuildContext context) {
@ -265,16 +279,17 @@ void main() {
preferBelow: true, preferBelow: true,
child: new Container( child: new Container(
width: 0.0, 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 (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) 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 { testWidgets('Does tooltip end up in the right place - way off to the right', (WidgetTester tester) async {
final GlobalKey key = new GlobalKey(); final GlobalKey key = new GlobalKey();
await tester.pumpWidget( await tester.pumpWidget(
new Overlay( new Directionality(
textDirection: TextDirection.ltr,
child: new Overlay(
initialEntries: <OverlayEntry>[ initialEntries: <OverlayEntry>[
new OverlayEntry( new OverlayEntry(
builder: (BuildContext context) { builder: (BuildContext context) {
@ -315,16 +332,17 @@ void main() {
preferBelow: true, preferBelow: true,
child: new Container( child: new Container(
width: 0.0, 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 (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) 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 { testWidgets('Does tooltip end up in the right place - near the edge', (WidgetTester tester) async {
final GlobalKey key = new GlobalKey(); final GlobalKey key = new GlobalKey();
await tester.pumpWidget( await tester.pumpWidget(
new Overlay( new Directionality(
textDirection: TextDirection.ltr,
child: new Overlay(
initialEntries: <OverlayEntry>[ initialEntries: <OverlayEntry>[
new OverlayEntry( new OverlayEntry(
builder: (BuildContext context) { builder: (BuildContext context) {
@ -367,16 +387,17 @@ void main() {
preferBelow: true, preferBelow: true,
child: new Container( child: new Container(
width: 0.0, 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 (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) 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(); final GlobalKey key = new GlobalKey();
await tester.pumpWidget( await tester.pumpWidget(
new Overlay( new Directionality(
textDirection: TextDirection.ltr,
child: new Overlay(
initialEntries: <OverlayEntry>[ initialEntries: <OverlayEntry>[
new OverlayEntry( new OverlayEntry(
builder: (BuildContext context) { builder: (BuildContext context) {
@ -450,15 +473,16 @@ void main() {
child: new Tooltip( child: new Tooltip(
key: key, key: key,
message: tooltipText, 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))); expect(semantics, hasSemantics(new TestSemantics.root(label: tooltipText)));
@ -504,7 +528,8 @@ void main() {
testWidgets('Haptic feedback', (WidgetTester tester) async { testWidgets('Haptic feedback', (WidgetTester tester) async {
final FeedbackTester feedback = new FeedbackTester(); final FeedbackTester feedback = new FeedbackTester();
await tester.pumpWidget(new MaterialApp( await tester.pumpWidget(
new MaterialApp(
home: new Center( home: new Center(
child: new Tooltip( child: new Tooltip(
message: 'Foo', message: 'Foo',
@ -512,10 +537,10 @@ void main() {
width: 100.0, width: 100.0,
height: 100.0, height: 100.0,
color: Colors.green[500], color: Colors.green[500],
) ),
) ),
) ),
) ),
); );
await tester.longPress(find.byType(Tooltip)); 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 // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:io' as io;
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:flutter/painting.dart'; import 'package:flutter/painting.dart';
@ -10,7 +9,8 @@ import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
test('TextPainter caret test', () { test('TextPainter caret test', () {
final TextPainter painter = new TextPainter(); final TextPainter painter = new TextPainter()
..textDirection = TextDirection.ltr;
String text = 'A'; String text = 'A';
painter.text = new TextSpan(text: text); painter.text = new TextSpan(text: text);
@ -27,13 +27,20 @@ void main() {
painter.layout(); painter.layout();
caretOffset = painter.getOffsetForCaret(new ui.TextPosition(offset: text.length), ui.Rect.zero); caretOffset = painter.getOffsetForCaret(new ui.TextPosition(offset: text.length), ui.Rect.zero);
expect(caretOffset.dx, painter.width); 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', () { test('TextPainter error test', () {
final TextPainter painter = new TextPainter(); final TextPainter painter = new TextPainter(textDirection: TextDirection.ltr);
expect(() { painter.paint(null, Offset.zero); }, throwsFlutterError); 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', () { test('TextPainter size test', () {
final TextPainter painter = new TextPainter( final TextPainter painter = new TextPainter(
text: const TextSpan( text: const TextSpan(
@ -44,20 +51,27 @@ void main() {
fontSize: 123.0, fontSize: 123.0,
), ),
), ),
textDirection: TextDirection.ltr,
); );
painter.layout(); painter.layout();
expect(painter.size, const Size(123.0, 123.0)); expect(painter.size, const Size(123.0, 123.0));
}); });
test('TextPainter default text height is 14 pixels', () { 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(); painter.layout();
expect(painter.preferredLineHeight, 14.0); expect(painter.preferredLineHeight, 14.0);
expect(painter.size, const Size(14.0, 14.0)); expect(painter.size, const Size(14.0, 14.0));
}); });
test('TextPainter sets paragraph size from root', () { 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(); painter.layout();
expect(painter.preferredLineHeight, 100.0); expect(painter.preferredLineHeight, 100.0);
expect(painter.size, const Size(100.0, 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'; import 'package:test/test.dart';
void main() { void main() {
test("TextStyle control test", () { test('TextStyle control test', () {
expect( expect(
const TextStyle(inherit: false).toString(), const TextStyle(inherit: false).toString(),
equals('TextStyle(inherit: false, <no style specified>)'), equals('TextStyle(inherit: false, <no style specified>)'),
@ -117,9 +117,17 @@ void main() {
final ui.ParagraphStyle ps2 = s2.getParagraphStyle(textAlign: TextAlign.center); 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, 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(); final ui.ParagraphStyle ps5 = s5.getParagraphStyle();
expect(ps5, equals(new ui.ParagraphStyle(fontWeight: FontWeight.w700, fontSize: 12.0, lineHeight: 123.0))); 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, hasAGoodToStringDeep);
expect(coloredBox.toStringDeep(), equalsIgnoringHashCodes( expect(coloredBox.toStringDeep(), equalsIgnoringHashCodes(
'RenderDecoratedBox#00000 NEEDS-LAYOUT NEEDS-PAINT DETACHED\n' 'RenderDecoratedBox#00000 NEEDS-LAYOUT NEEDS-PAINT DETACHED\n'
' parentData: null\n' ' parentData: MISSING\n'
' constraints: null\n' ' constraints: MISSING\n'
' size: MISSING\n' ' size: MISSING\n'
' decoration: BoxDecoration:\n' ' decoration: BoxDecoration:\n'
' <no decorations specified>\n' ' <no decorations specified>\n'

View File

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

View File

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

View File

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

View File

@ -10,8 +10,9 @@ void main() {
final RenderParagraph paragraph = new RenderParagraph( final RenderParagraph paragraph = new RenderParagraph(
const TextSpan( const TextSpan(
style: const TextStyle(height: 1.0), style: const TextStyle(height: 1.0),
text: 'Hello World' text: 'Hello World',
) ),
textDirection: TextDirection.ltr,
); );
final RenderListBody testBlock = new RenderListBody( final RenderListBody testBlock = new RenderListBody(
children: <RenderBox>[ 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() { void main() {
test('getOffsetForCaret control test', () { 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); layout(paragraph);
final Rect caret = new Rect.fromLTWH(0.0, 0.0, 2.0, 20.0); final Rect caret = new Rect.fromLTWH(0.0, 0.0, 2.0, 20.0);
@ -30,7 +33,10 @@ void main() {
}); });
test('getPositionForOffset control test', () { 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); layout(paragraph);
final TextPosition position20 = paragraph.getPositionForOffset(const Offset(20.0, 5.0)); final TextPosition position20 = paragraph.getPositionForOffset(const Offset(20.0, 5.0));
@ -44,7 +50,10 @@ void main() {
}); });
test('getBoxesForSelection control test', () { 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); layout(paragraph);
List<ui.TextBox> boxes = paragraph.getBoxesForSelection( List<ui.TextBox> boxes = paragraph.getBoxesForSelection(
@ -61,7 +70,10 @@ void main() {
}); });
test('getWordBoundary control test', () { 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); layout(paragraph);
final TextRange range5 = paragraph.getWordBoundary(const TextPosition(offset: 5)); 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.', '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), style: const TextStyle(fontFamily: 'Ahem', fontSize: 10.0),
), ),
textDirection: TextDirection.ltr,
maxLines: 1, maxLines: 1,
softWrap: true, softWrap: true,
); );
@ -157,6 +170,7 @@ void main() {
// 0 1 2 3 4 5 6 7 8 9 10 11 12 // 0 1 2 3 4 5 6 7 8 9 10 11 12
style: const TextStyle(fontFamily: 'Ahem', fontSize: 10.0), style: const TextStyle(fontFamily: 'Ahem', fontSize: 10.0),
), ),
textDirection: TextDirection.ltr,
); );
layout(paragraph, constraints: const BoxConstraints(maxWidth: 100.0)); layout(paragraph, constraints: const BoxConstraints(maxWidth: 100.0));
void layoutAt(int maxLines) { void layoutAt(int maxLines) {
@ -183,6 +197,7 @@ void main() {
text: 'Hello', text: 'Hello',
style: const TextStyle(color: const Color(0xFF000000)), style: const TextStyle(color: const Color(0xFF000000)),
), ),
textDirection: TextDirection.ltr,
); );
layout(paragraph, constraints: const BoxConstraints(maxWidth: 100.0), phase: EnginePhase.paint); layout(paragraph, constraints: const BoxConstraints(maxWidth: 100.0), phase: EnginePhase.paint);
expect(paragraph.debugNeedsLayout, isFalse); expect(paragraph.debugNeedsLayout, isFalse);
@ -210,15 +225,21 @@ void main() {
test('toStringDeep', () { test('toStringDeep', () {
final RenderParagraph paragraph = new RenderParagraph( final RenderParagraph paragraph = new RenderParagraph(
const TextSpan(text: _kText), const TextSpan(text: _kText),
textDirection: TextDirection.ltr,
); );
expect(paragraph, hasAGoodToStringDeep); expect(paragraph, hasAGoodToStringDeep);
expect( expect(
paragraph.toStringDeep(), paragraph.toStringDeep(),
equalsIgnoringHashCodes( equalsIgnoringHashCodes(
'RenderParagraph#00000 NEEDS-LAYOUT NEEDS-PAINT DETACHED\n' 'RenderParagraph#00000 NEEDS-LAYOUT NEEDS-PAINT DETACHED\n'
' │ parentData: null\n' ' │ parentData: MISSING\n'
' │ constraints: null\n' ' │ constraints: MISSING\n'
' │ size: 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' ' ╘═╦══ text ═══\n'
' ║ TextSpan:\n' ' ║ TextSpan:\n'
' ║ "I polished up that handle so carefullee\n' ' ║ "I polished up that handle so carefullee\n'

View File

@ -16,7 +16,12 @@ int countSemanticsChildren(RenderObject object) {
void main() { void main() {
test('RenderOpacity and children and semantics', () { 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); expect(countSemanticsChildren(box), 1);
box.opacity = 0.5; box.opacity = 0.5;
expect(countSemanticsChildren(box), 1); expect(countSemanticsChildren(box), 1);

View File

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

View File

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

View File

@ -230,7 +230,7 @@ void main() {
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
width: 100.0, width: 100.0,
height: 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), duration: const Duration(milliseconds: 200),
width: 200.0, width: 200.0,
height: 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), duration: const Duration(milliseconds: 200),
width: 200.0, width: 200.0,
height: 100.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 { testWidgets('AnimatedCrossFade.layoutBuilder', (WidgetTester tester) async {
await tester.pumpWidget(const AnimatedCrossFade( await tester.pumpWidget(const AnimatedCrossFade(
firstChild: const Text('AAA'), firstChild: const Text('AAA', textDirection: TextDirection.ltr),
secondChild: const Text('BBB'), secondChild: const Text('BBB', textDirection: TextDirection.ltr),
crossFadeState: CrossFadeState.showFirst, crossFadeState: CrossFadeState.showFirst,
duration: const Duration(milliseconds: 50), duration: const Duration(milliseconds: 50),
)); ));
expect(find.text('AAA'), findsOneWidget); expect(find.text('AAA'), findsOneWidget);
expect(find.text('BBB'), findsOneWidget); expect(find.text('BBB'), findsOneWidget);
await tester.pumpWidget(new AnimatedCrossFade( await tester.pumpWidget(new AnimatedCrossFade(
firstChild: const Text('AAA'), firstChild: const Text('AAA', textDirection: TextDirection.ltr),
secondChild: const Text('BBB'), secondChild: const Text('BBB', textDirection: TextDirection.ltr),
crossFadeState: CrossFadeState.showFirst, crossFadeState: CrossFadeState.showFirst,
duration: const Duration(milliseconds: 50), duration: const Duration(milliseconds: 50),
layoutBuilder: (Widget a, Key aKey, Widget b, Key bKey) => a, layoutBuilder: (Widget a, Key aKey, Widget b, Key bKey) => a,
@ -319,8 +319,8 @@ void main() {
expect(find.text('AAA'), findsOneWidget); expect(find.text('AAA'), findsOneWidget);
expect(find.text('BBB'), findsNothing); expect(find.text('BBB'), findsNothing);
await tester.pumpWidget(new AnimatedCrossFade( await tester.pumpWidget(new AnimatedCrossFade(
firstChild: const Text('AAA'), firstChild: const Text('AAA', textDirection: TextDirection.ltr),
secondChild: const Text('BBB'), secondChild: const Text('BBB', textDirection: TextDirection.ltr),
crossFadeState: CrossFadeState.showSecond, crossFadeState: CrossFadeState.showSecond,
duration: const Duration(milliseconds: 50), duration: const Duration(milliseconds: 50),
layoutBuilder: (Widget a, Key aKey, Widget b, Key bKey) => a, layoutBuilder: (Widget a, Key aKey, Widget b, Key bKey) => a,

View File

@ -121,7 +121,7 @@ void main() {
child: new SizedBox( child: new SizedBox(
height: 100.0, height: 100.0,
child: new Center( 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() { void main() {
Widget snapshotText(BuildContext context, AsyncSnapshot<String> snapshot) { Widget snapshotText(BuildContext context, AsyncSnapshot<String> snapshot) {
return new Text(snapshot.toString()); return new Text(snapshot.toString(), textDirection: TextDirection.ltr);
} }
group('AsyncSnapshot', () { group('AsyncSnapshot', () {
test('requiring data succeeds if data is present', () { 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'); List<String> afterDisconnected(List<String> current) => current..add('disc');
@override @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() { void main() {
test('A Banner with a location of topLeft paints in the top left', () { test('A Banner with a location of topLeft paints in the top left', () {
final BannerPainter bannerPainter = new BannerPainter( final BannerPainter bannerPainter = new BannerPainter(
message:"foo", message: 'foo',
textDirection: TextDirection.ltr,
location: BannerLocation.topLeft location: BannerLocation.topLeft
); );
@ -45,7 +46,8 @@ void main() {
test('A Banner with a location of topRight paints in the top right', () { test('A Banner with a location of topRight paints in the top right', () {
final BannerPainter bannerPainter = new BannerPainter( final BannerPainter bannerPainter = new BannerPainter(
message:"foo", message: 'foo',
textDirection: TextDirection.ltr,
location: BannerLocation.topRight location: BannerLocation.topRight
); );
@ -71,7 +73,8 @@ void main() {
test('A Banner with a location of bottomLeft paints in the bottom left', () { test('A Banner with a location of bottomLeft paints in the bottom left', () {
final BannerPainter bannerPainter = new BannerPainter( final BannerPainter bannerPainter = new BannerPainter(
message:"foo", message: 'foo',
textDirection: TextDirection.ltr,
location: BannerLocation.bottomLeft location: BannerLocation.bottomLeft
); );
@ -97,7 +100,8 @@ void main() {
test('A Banner with a location of bottomRight paints in the bottom right', () { test('A Banner with a location of bottomRight paints in the bottom right', () {
final BannerPainter bannerPainter = new BannerPainter( final BannerPainter bannerPainter = new BannerPainter(
message:"foo", message: 'foo',
textDirection: TextDirection.ltr,
location: BannerLocation.bottomRight location: BannerLocation.bottomRight
); );

View File

@ -14,7 +14,7 @@ void main() {
fontFamily: 'Ahem', fontFamily: 'Ahem',
fontSize: 100.0, fontSize: 100.0,
), ),
child: const Text('X'), child: const Text('X', textDirection: TextDirection.ltr),
), ),
), ),
); );
@ -32,7 +32,7 @@ void main() {
fontFamily: 'Ahem', fontFamily: 'Ahem',
fontSize: 100.0, 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, crossAxisAlignment: CrossAxisAlignment.start,
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
children: <Widget>[ 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()), 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() { void main() {
testWidgets('DefaultTextStyle changes propagate to Text', (WidgetTester tester) async { 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( const TextStyle s1 = const TextStyle(
fontSize: 10.0, fontSize: 10.0,
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
@ -16,7 +16,7 @@ void main() {
await tester.pumpWidget(const DefaultTextStyle( await tester.pumpWidget(const DefaultTextStyle(
style: s1, style: s1,
child: textWidget child: textWidget,
)); ));
RichText text = tester.firstWidget(find.byType(RichText)); RichText text = tester.firstWidget(find.byType(RichText));

View File

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

View File

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

View File

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

View File

@ -44,7 +44,7 @@ void main() {
width: 100.0, width: 100.0,
height: 100.0, height: 100.0,
color: const Color(0xFF0000FF), 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( child: new AnimatedBuilder(
animation: focusNode, animation: focusNode,
builder: (BuildContext context, Widget child) { 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; String fieldValue;
Widget builder() { Widget builder() {
return new Center( return new Directionality(
textDirection: TextDirection.ltr,
child: new Center(
child: new Material( child: new Material(
child: new Form( child: new Form(
key: formKey, key: formKey,
@ -20,6 +22,7 @@ void main() {
), ),
), ),
), ),
),
); );
} }
@ -42,7 +45,9 @@ void main() {
String fieldValue; String fieldValue;
Widget builder() { Widget builder() {
return new Center( return new Directionality(
textDirection: TextDirection.ltr,
child: new Center(
child: new Material( child: new Material(
child: new Form( child: new Form(
child: new TextField( child: new TextField(
@ -50,6 +55,7 @@ void main() {
), ),
), ),
), ),
),
); );
} }
@ -72,7 +78,9 @@ void main() {
String errorText(String value) => value + '/error'; String errorText(String value) => value + '/error';
Widget builder(bool autovalidate) { Widget builder(bool autovalidate) {
return new Center( return new Directionality(
textDirection: TextDirection.ltr,
child: new Center(
child: new Material( child: new Material(
child: new Form( child: new Form(
key: formKey, key: formKey,
@ -82,6 +90,7 @@ void main() {
), ),
), ),
), ),
),
); );
} }
@ -162,7 +171,9 @@ void main() {
final GlobalKey<FormFieldState<String>> inputKey = new GlobalKey<FormFieldState<String>>(); final GlobalKey<FormFieldState<String>> inputKey = new GlobalKey<FormFieldState<String>>();
Widget builder() { Widget builder() {
return new Center( return new Directionality(
textDirection: TextDirection.ltr,
child: new Center(
child: new Material( child: new Material(
child: new Form( child: new Form(
child: new TextFormField( child: new TextFormField(
@ -171,6 +182,7 @@ void main() {
), ),
), ),
), ),
),
); );
} }
@ -198,7 +210,9 @@ void main() {
String fieldValue; String fieldValue;
Widget builder(bool remove) { Widget builder(bool remove) {
return new Center( return new Directionality(
textDirection: TextDirection.ltr,
child: new Center(
child: new Material( child: new Material(
child: new Form( child: new Form(
key: formKey, key: formKey,
@ -209,6 +223,7 @@ void main() {
), ),
), ),
), ),
),
); );
} }

View File

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

View File

@ -8,7 +8,7 @@ import 'package:flutter/widgets.dart';
void main() { void main() {
testWidgets('toString control test', (WidgetTester tester) async { 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); final HitTestResult result = tester.hitTestOnBinding(Offset.zero);
expect(result, hasOneLineDescription); expect(result, hasOneLineDescription);
expect(result.path.first, hasOneLineDescription); expect(result.path.first, hasOneLineDescription);

View File

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

View File

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

View File

@ -92,7 +92,7 @@ class TriggerableState extends State<TriggerableWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
widget.counter.count++; 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) { builder: (BuildContext context) {
final ValueInherited v = context.inheritFromWidgetOfExactType(ValueInherited); final ValueInherited v = context.inheritFromWidgetOfExactType(ValueInherited);
log.add('a: ${v.value}'); log.add('a: ${v.value}');
return const Text(''); return const Text('', textDirection: TextDirection.ltr);
} }
) )
) )
@ -146,7 +146,7 @@ void main() {
builder: (BuildContext context) { builder: (BuildContext context) {
final ValueInherited v = context.inheritFromWidgetOfExactType(ValueInherited); final ValueInherited v = context.inheritFromWidgetOfExactType(ValueInherited);
log.add('b: ${v.value}'); log.add('b: ${v.value}');
return const Text(''); return const Text('', textDirection: TextDirection.ltr);
} }
) )
) )
@ -204,7 +204,7 @@ void main() {
builder: (BuildContext context) { builder: (BuildContext context) {
final ValueInherited v = context.inheritFromWidgetOfExactType(ValueInherited); final ValueInherited v = context.inheritFromWidgetOfExactType(ValueInherited);
log.add('a: ${v.value}'); log.add('a: ${v.value}');
return const Text(''); return const Text('', textDirection: TextDirection.ltr);
} }
) )
) )
@ -222,7 +222,7 @@ void main() {
builder: (BuildContext context) { builder: (BuildContext context) {
final ValueInherited v = context.inheritFromWidgetOfExactType(ValueInherited); final ValueInherited v = context.inheritFromWidgetOfExactType(ValueInherited);
log.add('b: ${v.value}'); log.add('b: ${v.value}');
return const Text(''); return const Text('', textDirection: TextDirection.ltr);
} }
) )
) )
@ -265,7 +265,7 @@ void main() {
builder: (BuildContext context) { builder: (BuildContext context) {
final ValueInherited v = context.inheritFromWidgetOfExactType(ValueInherited); final ValueInherited v = context.inheritFromWidgetOfExactType(ValueInherited);
log.add(v.value); log.add(v.value);
return const Text(''); return const Text('', textDirection: TextDirection.ltr);
} }
); );
@ -336,7 +336,7 @@ void main() {
builder: (BuildContext context) { builder: (BuildContext context) {
final ValueInherited v = context.inheritFromWidgetOfExactType(ValueInherited); final ValueInherited v = context.inheritFromWidgetOfExactType(ValueInherited);
log.add(v.value); 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), key: new ValueKey<int>(index),
width: 500.0, // this should be ignored width: 500.0, // this should be ignored
height: 400.0, // should be overridden by itemExtent 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, itemExtent: 200.0,
itemBuilder: itemBuilder, 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) { final IndexedWidgetBuilder itemBuilder = (BuildContext context, int index) {
callbackTracker.add(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( 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 // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.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]; 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