Commit f235a2c1 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

RTL: Padding, Flex (#11709)

* Introduce a Directionality inherited widget which sets the ambient LTR vs RTL mode (defaulting to null, which means you cannot use directionality-influenced values).

* Make it possible to configure Padding (including Container.padding and Container.margin) using a directionality-agnostic EdgeInsets variant.

* Provide textDirection and verticalDirection controls on Row and Column to make them RTL-aware.

* Introduce a variant of FractionalOffset based on the EdgeInsets variant. Not yet actually used.

* Fix all the tests that depended on Row defaulting to LTR.
parent 3dd1a05a
...@@ -9,7 +9,10 @@ import '../widgets/gestures.dart'; ...@@ -9,7 +9,10 @@ import '../widgets/gestures.dart';
void main() { void main() {
testWidgets('Tap on center change color', (WidgetTester tester) async { testWidgets('Tap on center change color', (WidgetTester tester) async {
await tester.pumpWidget(new GestureDemo()); await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr,
child: new GestureDemo(),
));
final Finder finder = find.byType(GestureDemo); final Finder finder = find.byType(GestureDemo);
MaterialColor getSwatch() => tester.state<GestureDemoState>(finder).swatch; MaterialColor getSwatch() => tester.state<GestureDemoState>(finder).swatch;
......
...@@ -26,11 +26,14 @@ const TextStyle _errorTextStyle = const TextStyle( ...@@ -26,11 +26,14 @@ const TextStyle _errorTextStyle = const TextStyle(
decorationStyle: TextDecorationStyle.double decorationStyle: TextDecorationStyle.double
); );
// Delegate that fetches the default (English) strings.
class _MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> { class _MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
const _MaterialLocalizationsDelegate(); const _MaterialLocalizationsDelegate();
@override @override
Future<MaterialLocalizations> load(Locale locale) => MaterialLocalizations.load(locale); Future<MaterialLocalizations> load(Locale locale) {
return new SynchronousFuture<MaterialLocalizations>(const MaterialLocalizations());
}
@override @override
bool shouldReload(_MaterialLocalizationsDelegate old) => false; bool shouldReload(_MaterialLocalizationsDelegate old) => false;
...@@ -317,8 +320,8 @@ class _MaterialAppState extends State<MaterialApp> { ...@@ -317,8 +320,8 @@ class _MaterialAppState extends State<MaterialApp> {
// Combine the Localizations for Material with the ones contributed // Combine the Localizations for Material with the ones contributed
// by the localizationsDelegates parameter, if any. // by the localizationsDelegates parameter, if any.
Iterable<LocalizationsDelegate<dynamic>> _createLocalizationsDelegates() sync* { Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates sync* {
yield const _MaterialLocalizationsDelegate(); yield const _MaterialLocalizationsDelegate(); // TODO(ianh): make this configurable
if (widget.localizationsDelegates != null) if (widget.localizationsDelegates != null)
yield* widget.localizationsDelegates; yield* widget.localizationsDelegates;
} }
...@@ -397,7 +400,7 @@ class _MaterialAppState extends State<MaterialApp> { ...@@ -397,7 +400,7 @@ class _MaterialAppState extends State<MaterialApp> {
onGenerateRoute: _onGenerateRoute, onGenerateRoute: _onGenerateRoute,
onUnknownRoute: _onUnknownRoute, onUnknownRoute: _onUnknownRoute,
locale: widget.locale, locale: widget.locale,
localizationsDelegates: _createLocalizationsDelegates(), localizationsDelegates: _localizationsDelegates,
showPerformanceOverlay: widget.showPerformanceOverlay, showPerformanceOverlay: widget.showPerformanceOverlay,
checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages, checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers, checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
......
...@@ -106,16 +106,16 @@ class Chip extends StatelessWidget { ...@@ -106,16 +106,16 @@ class Chip extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context)); assert(debugCheckHasMaterial(context));
final bool deletable = onDeleted != null; final bool deletable = onDeleted != null;
double leftPadding = 12.0; double startPadding = 12.0;
double rightPadding = 12.0; double endPadding = 12.0;
final List<Widget> children = <Widget>[]; final List<Widget> children = <Widget>[];
if (avatar != null) { if (avatar != null) {
leftPadding = 0.0; startPadding = 0.0;
children.add(new ExcludeSemantics( children.add(new ExcludeSemantics(
child: new Container( child: new Container(
margin: const EdgeInsets.only(right: 8.0), margin: const EdgeInsetsDirectional.only(end: 8.0),
width: _kAvatarDiamater, width: _kAvatarDiamater,
height: _kAvatarDiamater, height: _kAvatarDiamater,
child: avatar, child: avatar,
...@@ -131,7 +131,7 @@ class Chip extends StatelessWidget { ...@@ -131,7 +131,7 @@ class Chip extends StatelessWidget {
)); ));
if (deletable) { if (deletable) {
rightPadding = 0.0; endPadding = 0.0;
children.add(new GestureDetector( children.add(new GestureDetector(
onTap: Feedback.wrapForTap(onDeleted, context), onTap: Feedback.wrapForTap(onDeleted, context),
child: new Tooltip( child: new Tooltip(
...@@ -152,7 +152,7 @@ class Chip extends StatelessWidget { ...@@ -152,7 +152,7 @@ class Chip extends StatelessWidget {
container: true, container: true,
child: new Container( child: new Container(
height: _kChipHeight, height: _kChipHeight,
padding: new EdgeInsets.only(left: leftPadding, right: rightPadding), padding: new EdgeInsetsDirectional.only(start: startPadding, end: endPadding),
decoration: new BoxDecoration( decoration: new BoxDecoration(
color: backgroundColor ?? Colors.grey.shade300, color: backgroundColor ?? Colors.grey.shade300,
borderRadius: new BorderRadius.circular(16.0), borderRadius: new BorderRadius.circular(16.0),
......
...@@ -2,29 +2,35 @@ ...@@ -2,29 +2,35 @@
// 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:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
/// Default localized resource values for the material widgets. /// Interface for localized resource values for the material widgets.
/// ///
/// This class is just a placeholder, it only provides English values. /// This class provides a default placeholder implementation that returns
/// hard-coded American English values.
class MaterialLocalizations { class MaterialLocalizations {
const MaterialLocalizations._(this.locale) : assert(locale != null); /// Create a placeholder object for the localized resources of material widgets
/// which only provides American English strings.
const MaterialLocalizations();
/// The locale for which the values of this class's localized resources /// The locale for which the values of this class's localized resources
/// have been translated. /// have been translated.
final Locale locale; Locale get locale => const Locale('en', 'US');
/// Creates an object that provides default localized resource values for the /// The tooltip for the leading [AppBar] menu (aka 'hamburger') button
/// for the widgets of the material library. String get openAppDrawerTooltip => 'Open navigation menu';
///
/// This method is typically used to create a [DefaultLocalizationsDelegate]. /// The [BackButton]'s tooltip.
/// The [MaterialApp] does so by default. String get backButtonTooltip => 'Back';
static Future<MaterialLocalizations> load(Locale locale) {
return new SynchronousFuture<MaterialLocalizations>(new MaterialLocalizations._(locale)); /// The [CloseButton]'s tooltip.
} String get closeButtonTooltip => 'Close';
/// The tooltip for the [MonthPicker]'s "next month" button.
String get nextMonthTooltip => 'Next month';
/// The tooltip for the [MonthPicker]'s "previous month" button.
String get previousMonthTooltip => 'Previous month';
/// The `MaterialLocalizations` from the closest [Localizations] instance /// The `MaterialLocalizations` from the closest [Localizations] instance
/// that encloses the given context. /// that encloses the given context.
...@@ -34,25 +40,11 @@ class MaterialLocalizations { ...@@ -34,25 +40,11 @@ class MaterialLocalizations {
/// ///
/// References to the localized resources defined by this class are typically /// References to the localized resources defined by this class are typically
/// written in terms of this method. For example: /// written in terms of this method. For example:
///
/// ```dart /// ```dart
/// tooltip: MaterialLocalizations.of(context).backButtonTooltip, /// tooltip: MaterialLocalizations.of(context).backButtonTooltip,
/// ``` /// ```
static MaterialLocalizations of(BuildContext context) { static MaterialLocalizations of(BuildContext context) {
return Localizations.of<MaterialLocalizations>(context, MaterialLocalizations); return Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);
} }
/// The tooltip for the leading [AppBar] menu (aka 'hamburger') button
String get openAppDrawerTooltip => 'Open navigation menu';
/// The [BackButton]'s tooltip.
String get backButtonTooltip => 'Back';
/// The [CloseButton]'s tooltip.
String get closeButtonTooltip => 'Close';
/// The tooltip for the [MonthPicker]'s "next month" button.
String get nextMonthTooltip => 'Next month';
/// The tooltip for the [MonthPicker]'s "previous month" button.
String get previousMonthTooltip => 'Previous month';
} }
...@@ -150,6 +150,8 @@ class _TabLabelBarRenderer extends RenderFlex { ...@@ -150,6 +150,8 @@ class _TabLabelBarRenderer extends RenderFlex {
MainAxisSize mainAxisSize, MainAxisSize mainAxisSize,
MainAxisAlignment mainAxisAlignment, MainAxisAlignment mainAxisAlignment,
CrossAxisAlignment crossAxisAlignment, CrossAxisAlignment crossAxisAlignment,
TextDirection textDirection,
VerticalDirection verticalDirection,
TextBaseline textBaseline, TextBaseline textBaseline,
@required this.onPerformLayout, @required this.onPerformLayout,
}) : assert(onPerformLayout != null), }) : assert(onPerformLayout != null),
...@@ -159,6 +161,8 @@ class _TabLabelBarRenderer extends RenderFlex { ...@@ -159,6 +161,8 @@ class _TabLabelBarRenderer extends RenderFlex {
mainAxisSize: mainAxisSize, mainAxisSize: mainAxisSize,
mainAxisAlignment: mainAxisAlignment, mainAxisAlignment: mainAxisAlignment,
crossAxisAlignment: crossAxisAlignment, crossAxisAlignment: crossAxisAlignment,
textDirection: textDirection,
verticalDirection: verticalDirection,
textBaseline: textBaseline, textBaseline: textBaseline,
); );
...@@ -188,6 +192,8 @@ class _TabLabelBar extends Flex { ...@@ -188,6 +192,8 @@ class _TabLabelBar extends Flex {
Key key, Key key,
MainAxisAlignment mainAxisAlignment, MainAxisAlignment mainAxisAlignment,
CrossAxisAlignment crossAxisAlignment, CrossAxisAlignment crossAxisAlignment,
TextDirection textDirection,
VerticalDirection verticalDirection: VerticalDirection.down,
List<Widget> children: const <Widget>[], List<Widget> children: const <Widget>[],
this.onPerformLayout, this.onPerformLayout,
}) : super( }) : super(
...@@ -197,6 +203,8 @@ class _TabLabelBar extends Flex { ...@@ -197,6 +203,8 @@ class _TabLabelBar extends Flex {
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
textDirection: textDirection,
verticalDirection: verticalDirection,
); );
final ValueChanged<List<double>> onPerformLayout; final ValueChanged<List<double>> onPerformLayout;
...@@ -208,6 +216,8 @@ class _TabLabelBar extends Flex { ...@@ -208,6 +216,8 @@ class _TabLabelBar extends Flex {
mainAxisAlignment: mainAxisAlignment, mainAxisAlignment: mainAxisAlignment,
mainAxisSize: mainAxisSize, mainAxisSize: mainAxisSize,
crossAxisAlignment: crossAxisAlignment, crossAxisAlignment: crossAxisAlignment,
textDirection: getEffectiveTextDirection(context),
verticalDirection: verticalDirection,
textBaseline: textBaseline, textBaseline: textBaseline,
onPerformLayout: onPerformLayout, onPerformLayout: onPerformLayout,
); );
......
...@@ -90,3 +90,70 @@ enum RenderComparison { ...@@ -90,3 +90,70 @@ enum RenderComparison {
/// change in a render object. /// change in a render object.
layout, layout,
} }
/// The two cardinal directions in two dimensions.
///
/// The axis is always relative to the current coordinate space. This means, for
/// example, that a [horizontal] axis might actually be diagonally from top
/// right to bottom left, due to some local [Transform] applied to the scene.
///
/// See also:
///
/// * [AxisDirection], which is a directional version of this enum (with values
/// light left and right, rather than just horizontal).
/// * [TextDirection], which disambiguates between left-to-right horizontal
/// content and right-to-left horizontal content.
enum Axis {
/// Left and right.
///
/// See also:
///
/// * [TextDirection], which disambiguates between left-to-right horizontal
/// content and right-to-left horizontal content.
horizontal,
/// Up and down.
vertical,
}
/// Returns the opposite of the given [Axis].
///
/// Specifically, returns [Axis.horizontal] for [Axis.vertical], and
/// vice versa.
///
/// See also:
///
/// * [flipAxisDirection], which does the same thing for [AxisDirection] values.
Axis flipAxis(Axis direction) {
assert(direction != null);
switch (direction) {
case Axis.horizontal:
return Axis.vertical;
case Axis.vertical:
return Axis.horizontal;
}
return null;
}
/// A direction in which boxes flow vertically.
///
/// This is used by the flex algorithm (e.g. [Column]) to decide in which
/// direction to draw boxes.
///
/// This is also used to disambiguate `start` and `end` values (e.g.
/// [MainAxisAlignment.start] or [CrossAxisAlignment.end]).
///
/// See also:
///
/// * [TextDirection], which controls the same thing but horizontally.
enum VerticalDirection {
/// Boxes should start at the bottom and be stacked vertically towards the top.
///
/// The "start" is at the bottom, the "end" is at the top.
up,
/// Boxes should start at the top and be stacked vertically towards the bottom.
///
/// The "start" is at the top, the "end" is at the bottom.
down,
}
...@@ -2149,6 +2149,11 @@ abstract class RenderBoxContainerDefaultsMixin<ChildType extends RenderBox, Pare ...@@ -2149,6 +2149,11 @@ abstract class RenderBoxContainerDefaultsMixin<ChildType extends RenderBox, Pare
/// ///
/// Stops walking once after the first child reports that it contains the /// Stops walking once after the first child reports that it contains the
/// given point. Returns whether any children contain the given point. /// given point. Returns whether any children contain the given point.
///
/// See also:
///
/// * [defaultPaint], which paints the children appropriate for this
/// hit-testing strategy.
bool defaultHitTestChildren(HitTestResult result, { Offset position }) { bool defaultHitTestChildren(HitTestResult result, { Offset position }) {
// the x, y parameters have the top left of the node's box as the origin // the x, y parameters have the top left of the node's box as the origin
ChildType child = lastChild; ChildType child = lastChild;
...@@ -2162,6 +2167,11 @@ abstract class RenderBoxContainerDefaultsMixin<ChildType extends RenderBox, Pare ...@@ -2162,6 +2167,11 @@ abstract class RenderBoxContainerDefaultsMixin<ChildType extends RenderBox, Pare
} }
/// Paints each child by walking the child list forwards. /// Paints each child by walking the child list forwards.
///
/// See also:
///
/// * [defaultHitTestChildren], which implements hit-testing of the children
/// in a manner appropriate for this painting strategy.
void defaultPaint(PaintingContext context, Offset offset) { void defaultPaint(PaintingContext context, Offset offset) {
ChildType child = firstChild; ChildType child = firstChild;
while (child != null) { while (child != null) {
......
...@@ -43,7 +43,7 @@ class AbstractNode { ...@@ -43,7 +43,7 @@ class AbstractNode {
int get depth => _depth; int get depth => _depth;
int _depth = 0; int _depth = 0;
/// Adjust the [depth] of the given [child] to be greated than this node's own /// Adjust the [depth] of the given [child] to be greater than this node's own
/// [depth]. /// [depth].
/// ///
/// Only call this method from overrides of [redepthChildren]. /// Only call this method from overrides of [redepthChildren].
......
...@@ -91,29 +91,58 @@ class RenderPadding extends RenderShiftedBox { ...@@ -91,29 +91,58 @@ class RenderPadding extends RenderShiftedBox {
/// ///
/// The [padding] argument must not be null and must have non-negative insets. /// The [padding] argument must not be null and must have non-negative insets.
RenderPadding({ RenderPadding({
@required EdgeInsets padding, @required EdgeInsetsGeometry padding,
RenderBox child TextDirection textDirection,
RenderBox child,
}) : assert(padding != null), }) : assert(padding != null),
assert(padding.isNonNegative), assert(padding.isNonNegative),
_textDirection = textDirection,
_padding = padding, _padding = padding,
super(child); super(child) {
_applyUpdate();
}
// The resolved absolute insets.
EdgeInsets _resolvedPadding;
void _applyUpdate() {
final EdgeInsets resolvedPadding = padding.resolve(textDirection);
assert(resolvedPadding.isNonNegative);
if (resolvedPadding != _resolvedPadding) {
_resolvedPadding = resolvedPadding;
markNeedsLayout();
}
}
/// The amount to pad the child in each dimension. /// The amount to pad the child in each dimension.
EdgeInsets get padding => _padding; ///
EdgeInsets _padding; /// If this is set to an [EdgeInsetsDirectional] object, then [textDirection]
set padding(EdgeInsets value) { /// must be non-null.
EdgeInsetsGeometry get padding => _padding;
EdgeInsetsGeometry _padding;
set padding(EdgeInsetsGeometry value) {
assert(value != null); assert(value != null);
assert(value.isNonNegative); assert(value.isNonNegative);
if (_padding == value) if (_padding == value)
return; return;
_padding = value; _padding = value;
markNeedsLayout(); _applyUpdate();
}
/// The text direction with which to resolve [padding].
TextDirection get textDirection => _textDirection;
TextDirection _textDirection;
set textDirection(TextDirection value) {
if (_textDirection == value)
return;
_textDirection = value;
_applyUpdate();
} }
@override @override
double computeMinIntrinsicWidth(double height) { double computeMinIntrinsicWidth(double height) {
final double totalHorizontalPadding = padding.left + padding.right; final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
final double totalVerticalPadding = padding.top + padding.bottom; final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
if (child != null) // next line relies on double.INFINITY absorption if (child != null) // next line relies on double.INFINITY absorption
return child.getMinIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding; return child.getMinIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding;
return totalHorizontalPadding; return totalHorizontalPadding;
...@@ -121,8 +150,8 @@ class RenderPadding extends RenderShiftedBox { ...@@ -121,8 +150,8 @@ class RenderPadding extends RenderShiftedBox {
@override @override
double computeMaxIntrinsicWidth(double height) { double computeMaxIntrinsicWidth(double height) {
final double totalHorizontalPadding = padding.left + padding.right; final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
final double totalVerticalPadding = padding.top + padding.bottom; final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
if (child != null) // next line relies on double.INFINITY absorption if (child != null) // next line relies on double.INFINITY absorption
return child.getMaxIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding; return child.getMaxIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding;
return totalHorizontalPadding; return totalHorizontalPadding;
...@@ -130,8 +159,8 @@ class RenderPadding extends RenderShiftedBox { ...@@ -130,8 +159,8 @@ class RenderPadding extends RenderShiftedBox {
@override @override
double computeMinIntrinsicHeight(double width) { double computeMinIntrinsicHeight(double width) {
final double totalHorizontalPadding = padding.left + padding.right; final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
final double totalVerticalPadding = padding.top + padding.bottom; final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
if (child != null) // next line relies on double.INFINITY absorption if (child != null) // next line relies on double.INFINITY absorption
return child.getMinIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding; return child.getMinIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding;
return totalVerticalPadding; return totalVerticalPadding;
...@@ -139,8 +168,8 @@ class RenderPadding extends RenderShiftedBox { ...@@ -139,8 +168,8 @@ class RenderPadding extends RenderShiftedBox {
@override @override
double computeMaxIntrinsicHeight(double width) { double computeMaxIntrinsicHeight(double width) {
final double totalHorizontalPadding = padding.left + padding.right; final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
final double totalVerticalPadding = padding.top + padding.bottom; final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
if (child != null) // next line relies on double.INFINITY absorption if (child != null) // next line relies on double.INFINITY absorption
return child.getMaxIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding; return child.getMaxIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding;
return totalVerticalPadding; return totalVerticalPadding;
...@@ -148,21 +177,21 @@ class RenderPadding extends RenderShiftedBox { ...@@ -148,21 +177,21 @@ class RenderPadding extends RenderShiftedBox {
@override @override
void performLayout() { void performLayout() {
assert(padding != null); assert(_resolvedPadding != null);
if (child == null) { if (child == null) {
size = constraints.constrain(new Size( size = constraints.constrain(new Size(
padding.left + padding.right, _resolvedPadding.left + _resolvedPadding.right,
padding.top + padding.bottom _resolvedPadding.top + _resolvedPadding.bottom
)); ));
return; return;
} }
final BoxConstraints innerConstraints = constraints.deflate(padding); final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding);
child.layout(innerConstraints, parentUsesSize: true); child.layout(innerConstraints, parentUsesSize: true);
final BoxParentData childParentData = child.parentData; final BoxParentData childParentData = child.parentData;
childParentData.offset = new Offset(padding.left, padding.top); childParentData.offset = new Offset(_resolvedPadding.left, _resolvedPadding.top);
size = constraints.constrain(new Size( size = constraints.constrain(new Size(
padding.left + child.size.width + padding.right, _resolvedPadding.left + child.size.width + _resolvedPadding.right,
padding.top + child.size.height + padding.bottom _resolvedPadding.top + child.size.height + _resolvedPadding.bottom
)); ));
} }
...@@ -171,7 +200,7 @@ class RenderPadding extends RenderShiftedBox { ...@@ -171,7 +200,7 @@ class RenderPadding extends RenderShiftedBox {
super.debugPaintSize(context, offset); super.debugPaintSize(context, offset);
assert(() { assert(() {
final Rect outerRect = offset & size; final Rect outerRect = offset & size;
debugPaintPadding(context.canvas, outerRect, child != null ? padding.deflateRect(outerRect) : null); debugPaintPadding(context.canvas, outerRect, child != null ? _resolvedPadding.deflateRect(outerRect) : null);
return true; return true;
}); });
} }
...@@ -179,7 +208,8 @@ class RenderPadding extends RenderShiftedBox { ...@@ -179,7 +208,8 @@ class RenderPadding extends RenderShiftedBox {
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder description) { void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description); super.debugFillProperties(description);
description.add(new DiagnosticsProperty<EdgeInsets>('padding', padding)); description.add(new DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
description.add(new EnumProperty<TextDirection>('textDirection', textDirection));
} }
} }
......
...@@ -94,6 +94,10 @@ Axis axisDirectionToAxis(AxisDirection axisDirection) { ...@@ -94,6 +94,10 @@ Axis axisDirectionToAxis(AxisDirection axisDirection) {
/// Specifically, returns [AxisDirection.up] for [AxisDirection.down] (and /// Specifically, returns [AxisDirection.up] for [AxisDirection.down] (and
/// vice versa), as well as [AxisDirection.left] for [AxisDirection.right] (and /// vice versa), as well as [AxisDirection.left] for [AxisDirection.right] (and
/// vice versa). /// vice versa).
///
/// See also:
///
/// * [flipAxis], which does the same thing for [Axis] values.
AxisDirection flipAxisDirection(AxisDirection axisDirection) { AxisDirection flipAxisDirection(AxisDirection axisDirection) {
assert(axisDirection != null); assert(axisDirection != null);
switch (axisDirection) { switch (axisDirection) {
......
...@@ -23,6 +23,19 @@ import 'widget_inspector.dart'; ...@@ -23,6 +23,19 @@ import 'widget_inspector.dart';
export 'dart:ui' show Locale; export 'dart:ui' show Locale;
// Delegate that fetches the default (English) strings.
class _WidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
const _WidgetsLocalizationsDelegate();
@override
Future<WidgetsLocalizations> load(Locale locale) {
return new SynchronousFuture<WidgetsLocalizations>(const WidgetsLocalizations());
}
@override
bool shouldReload(_WidgetsLocalizationsDelegate old) => false;
}
/// A convenience class that wraps a number of widgets that are commonly /// A convenience class that wraps a number of widgets that are commonly
/// required for an application. /// required for an application.
/// ///
...@@ -269,6 +282,14 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv ...@@ -269,6 +282,14 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
} }
} }
// Combine the Localizations for Widgets with the ones contributed
// by the localizationsDelegates parameter, if any.
Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates sync* {
yield const _WidgetsLocalizationsDelegate(); // TODO(ianh): make this configurable
if (widget.localizationsDelegates != null)
yield* widget.localizationsDelegates;
}
@override @override
void didChangeAppLifecycleState(AppLifecycleState state) { } void didChangeAppLifecycleState(AppLifecycleState state) { }
...@@ -281,7 +302,7 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv ...@@ -281,7 +302,7 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
data: new MediaQueryData.fromWindow(ui.window), data: new MediaQueryData.fromWindow(ui.window),
child: new Localizations( child: new Localizations(
locale: widget.locale ?? _locale, locale: widget.locale ?? _locale,
delegates: widget.localizationsDelegates, delegates: _localizationsDelegates.toList(),
child: new Title( child: new Title(
title: widget.title, title: widget.title,
color: widget.color, color: widget.color,
......
This diff is collapsed.
...@@ -282,7 +282,7 @@ class Container extends StatelessWidget { ...@@ -282,7 +282,7 @@ class Container extends StatelessWidget {
/// Empty space to inscribe inside the [decoration]. The [child], if any, is /// Empty space to inscribe inside the [decoration]. The [child], if any, is
/// placed inside this padding. /// placed inside this padding.
final EdgeInsets padding; final EdgeInsetsGeometry padding;
/// The decoration to paint behind the [child]. /// The decoration to paint behind the [child].
/// ///
...@@ -303,18 +303,18 @@ class Container extends StatelessWidget { ...@@ -303,18 +303,18 @@ class Container extends StatelessWidget {
final BoxConstraints constraints; final BoxConstraints constraints;
/// Empty space to surround the [decoration] and [child]. /// Empty space to surround the [decoration] and [child].
final EdgeInsets margin; final EdgeInsetsGeometry margin;
/// The transformation matrix to apply before painting the container. /// The transformation matrix to apply before painting the container.
final Matrix4 transform; final Matrix4 transform;
EdgeInsets get _paddingIncludingDecoration { EdgeInsetsGeometry get _paddingIncludingDecoration {
if (decoration == null || decoration.padding == null) if (decoration == null || decoration.padding == null)
return padding; return padding;
final EdgeInsets decorationPadding = decoration.padding; final EdgeInsetsGeometry decorationPadding = decoration.padding;
if (padding == null) if (padding == null)
return decorationPadding; return decorationPadding;
return padding + decorationPadding; return padding.add(decorationPadding);
} }
@override @override
...@@ -332,7 +332,7 @@ class Container extends StatelessWidget { ...@@ -332,7 +332,7 @@ class Container extends StatelessWidget {
if (alignment != null) if (alignment != null)
current = new Align(alignment: alignment, child: current); current = new Align(alignment: alignment, child: current);
final EdgeInsets effectivePadding = _paddingIncludingDecoration; final EdgeInsetsGeometry effectivePadding = _paddingIncludingDecoration;
if (effectivePadding != null) if (effectivePadding != null)
current = new Padding(padding: effectivePadding, child: current); current = new Padding(padding: effectivePadding, child: current);
...@@ -363,11 +363,11 @@ class Container extends StatelessWidget { ...@@ -363,11 +363,11 @@ class Container extends StatelessWidget {
void debugFillProperties(DiagnosticPropertiesBuilder description) { void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description); super.debugFillProperties(description);
description.add(new DiagnosticsProperty<FractionalOffset>('alignment', alignment, showName: false, defaultValue: null)); description.add(new DiagnosticsProperty<FractionalOffset>('alignment', alignment, showName: false, defaultValue: null));
description.add(new DiagnosticsProperty<EdgeInsets>('padding', padding, defaultValue: null)); description.add(new DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
description.add(new DiagnosticsProperty<Decoration>('bg', decoration, defaultValue: null)); description.add(new DiagnosticsProperty<Decoration>('bg', decoration, defaultValue: null));
description.add(new DiagnosticsProperty<Decoration>('fg', foregroundDecoration, defaultValue: null)); description.add(new DiagnosticsProperty<Decoration>('fg', foregroundDecoration, defaultValue: null));
description.add(new DiagnosticsProperty<BoxConstraints>('constraints', constraints, defaultValue: null)); description.add(new DiagnosticsProperty<BoxConstraints>('constraints', constraints, defaultValue: null));
description.add(new DiagnosticsProperty<EdgeInsets>('margin', margin, defaultValue: null)); description.add(new DiagnosticsProperty<EdgeInsetsGeometry>('margin', margin, defaultValue: null));
description.add(new ObjectFlagProperty<Matrix4>.has('transform', transform)); description.add(new ObjectFlagProperty<Matrix4>.has('transform', transform));
} }
} }
...@@ -7,6 +7,7 @@ import 'dart:ui' show Locale; ...@@ -7,6 +7,7 @@ import 'dart:ui' show Locale;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'basic.dart';
import 'binding.dart'; import 'binding.dart';
import 'container.dart'; import 'container.dart';
import 'framework.dart'; import 'framework.dart';
...@@ -90,6 +91,47 @@ abstract class LocalizationsDelegate<T> { ...@@ -90,6 +91,47 @@ abstract class LocalizationsDelegate<T> {
/// rebuilt. If it returns true then dependent widgets will be rebuilt /// rebuilt. If it returns true then dependent widgets will be rebuilt
/// after [load] has completed. /// after [load] has completed.
bool shouldReload(covariant LocalizationsDelegate<T> old); bool shouldReload(covariant LocalizationsDelegate<T> old);
@override
String toString() => '$runtimeType';
}
/// Interface for localized resource values for the lowest levels of the Flutter
/// framework.
///
/// In particular, this maps locales to a specific [Directionality] using the
/// [textDirection] property.
///
/// This class provides a default placeholder implementation that returns
/// hard-coded American English values.
class WidgetsLocalizations {
/// Create a placeholder object for the localized resources of the lowest
/// levels of the Flutter framework which only provides values for American
/// English.
const WidgetsLocalizations();
/// The locale for which the values of this class's localized resources
/// have been translated.
Locale get locale => const Locale('en', 'US');
/// The reading direction for text in this locale.
TextDirection get textDirection => TextDirection.ltr;
/// The `WidgetsLocalizations` from the closest [Localizations] instance
/// that encloses the given context.
///
/// This method is just a convenient shorthand for:
/// `Localizations.of<WidgetsLocalizations>(context, WidgetsLocalizations)`.
///
/// References to the localized resources defined by this class are typically
/// written in terms of this method. For example:
///
/// ```dart
/// textDirection: WidgetsLocalizations.of(context).textDirection,
/// ```
static WidgetsLocalizations of(BuildContext context) {
return Localizations.of<WidgetsLocalizations>(context, WidgetsLocalizations);
}
} }
class _LocalizationsScope extends InheritedWidget { class _LocalizationsScope extends InheritedWidget {
...@@ -158,7 +200,7 @@ class _LocalizationsScope extends InheritedWidget { ...@@ -158,7 +200,7 @@ class _LocalizationsScope extends InheritedWidget {
/// ///
/// This class is effectively an [InheritedWidget]. If it's rebuilt with /// This class is effectively an [InheritedWidget]. If it's rebuilt with
/// a new `locale` or a different list of delegates or any of its /// a new `locale` or a different list of delegates or any of its
/// delegates' [LocalizationDelegate.shouldReload()] methods returns true, /// delegates' [LocalizationsDelegate.shouldReload()] methods returns true,
/// then widgets that have created a dependency by calling /// then widgets that have created a dependency by calling
/// `Localizations.of(context)` will be rebuilt after the resources /// `Localizations.of(context)` will be rebuilt after the resources
/// for the new locale have been loaded. /// for the new locale have been loaded.
...@@ -199,21 +241,25 @@ class _LocalizationsScope extends InheritedWidget { ...@@ -199,21 +241,25 @@ class _LocalizationsScope extends InheritedWidget {
/// One could choose another approach for loading localized resources and looking them up while /// One could choose another approach for loading localized resources and looking them up while
/// still conforming to the structure of this example. /// still conforming to the structure of this example.
class Localizations extends StatefulWidget { class Localizations extends StatefulWidget {
/// Create a widget from which ambient localizations (translated strings)
/// can be obtained.
Localizations({ Localizations({
Key key, Key key,
@required this.locale, @required this.locale,
this.delegates, @required this.delegates,
this.child this.child,
}) : super(key: key) { }) : assert(locale != null),
assert(locale != null); assert(delegates != null),
super(key: key) {
assert(delegates.any((LocalizationsDelegate<dynamic> delegate) => delegate is LocalizationsDelegate<WidgetsLocalizations>));
} }
/// The resources returned by [Localizations.of] will be specific to this locale. /// The resources returned by [Localizations.of] will be specific to this locale.
final Locale locale; final Locale locale;
/// This list collectively defines the localized resources objects that can /// This list collectively defines the localized resources objects that can
/// be retrieved with [ Localizations.of]. /// be retrieved with [Localizations.of].
final Iterable<LocalizationsDelegate<dynamic>> delegates; final List<LocalizationsDelegate<dynamic>> delegates;
/// The widget below this widget in the tree. /// The widget below this widget in the tree.
final Widget child; final Widget child;
...@@ -247,6 +293,13 @@ class Localizations extends StatefulWidget { ...@@ -247,6 +293,13 @@ class Localizations extends StatefulWidget {
@override @override
_LocalizationsState createState() => new _LocalizationsState(); _LocalizationsState createState() => new _LocalizationsState();
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
description.add(new DiagnosticsProperty<Locale>('locale', locale));
description.add(new IterableProperty<LocalizationsDelegate<dynamic>>('delegates', delegates));
}
} }
class _LocalizationsState extends State<Localizations> { class _LocalizationsState extends State<Localizations> {
...@@ -329,18 +382,29 @@ class _LocalizationsState extends State<Localizations> { ...@@ -329,18 +382,29 @@ class _LocalizationsState extends State<Localizations> {
T resourcesFor<T>(Type type) { T resourcesFor<T>(Type type) {
assert(type != null); assert(type != null);
final dynamic resources = _typeToResources[type]; final T resources = _typeToResources[type];
assert(resources.runtimeType == type); assert(resources.runtimeType == type);
return resources; return resources;
} }
TextDirection get _textDirection {
final WidgetsLocalizations resources = _typeToResources[WidgetsLocalizations];
assert(resources != null);
return resources.textDirection;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (_locale == null)
return new Container();
return new _LocalizationsScope( return new _LocalizationsScope(
key: _localizedResourcesScopeKey, key: _localizedResourcesScopeKey,
locale: widget.locale, locale: _locale,
localizationsState: this, localizationsState: this,
child: _locale != null ? widget.child : new Container(), child: new Directionality(
textDirection: _textDirection,
child: widget.child,
),
); );
} }
} }
...@@ -264,7 +264,7 @@ class ScrollController extends ChangeNotifier { ...@@ -264,7 +264,7 @@ class ScrollController extends ChangeNotifier {
description.add('no clients'); description.add('no clients');
} else if (_positions.length == 1) { } else if (_positions.length == 1) {
// Don't actually list the client itself, since its toString may refer to us. // Don't actually list the client itself, since its toString may refer to us.
description.add('one client, offset ${offset.toStringAsFixed(1)}'); description.add('one client, offset ${offset?.toStringAsFixed(1)}');
} else { } else {
description.add('${_positions.length} clients'); description.add('${_positions.length} clients');
} }
......
...@@ -7,10 +7,19 @@ import 'package:flutter_test/flutter_test.dart'; ...@@ -7,10 +7,19 @@ import 'package:flutter_test/flutter_test.dart';
import '../services/mocks_for_image_cache.dart'; import '../services/mocks_for_image_cache.dart';
Future<Null> pumpWidgetWithBoilerplate(WidgetTester tester, Widget widget) async {
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: widget,
),
);
}
void main() { void main() {
testWidgets('Need at least 2 tabs', (WidgetTester tester) async { testWidgets('Need at least 2 tabs', (WidgetTester tester) async {
try { try {
await tester.pumpWidget(new CupertinoTabBar( await pumpWidgetWithBoilerplate(tester, new CupertinoTabBar(
items: <BottomNavigationBarItem>[ items: <BottomNavigationBarItem>[
const BottomNavigationBarItem( const BottomNavigationBarItem(
icon: const ImageIcon(const TestImageProvider(24, 24)), icon: const ImageIcon(const TestImageProvider(24, 24)),
...@@ -19,13 +28,14 @@ void main() { ...@@ -19,13 +28,14 @@ void main() {
], ],
)); ));
fail('Should not be possible to create a tab bar with just one item'); fail('Should not be possible to create a tab bar with just one item');
} on AssertionError { } on AssertionError catch (e) {
expect(e.toString(), contains('items.length'));
// Exception expected. // Exception expected.
} }
}); });
testWidgets('Active and inactive colors', (WidgetTester tester) async { testWidgets('Active and inactive colors', (WidgetTester tester) async {
await tester.pumpWidget(new CupertinoTabBar( await pumpWidgetWithBoilerplate(tester, new CupertinoTabBar(
items: <BottomNavigationBarItem>[ items: <BottomNavigationBarItem>[
const BottomNavigationBarItem( const BottomNavigationBarItem(
icon: const ImageIcon(const TestImageProvider(24, 24)), icon: const ImageIcon(const TestImageProvider(24, 24)),
...@@ -55,7 +65,7 @@ void main() { ...@@ -55,7 +65,7 @@ void main() {
}); });
testWidgets('Opaque background does not add blur effects', (WidgetTester tester) async { testWidgets('Opaque background does not add blur effects', (WidgetTester tester) async {
await tester.pumpWidget(new CupertinoTabBar( await pumpWidgetWithBoilerplate(tester, new CupertinoTabBar(
items: <BottomNavigationBarItem>[ items: <BottomNavigationBarItem>[
const BottomNavigationBarItem( const BottomNavigationBarItem(
icon: const ImageIcon(const TestImageProvider(24, 24)), icon: const ImageIcon(const TestImageProvider(24, 24)),
...@@ -70,7 +80,7 @@ void main() { ...@@ -70,7 +80,7 @@ void main() {
expect(find.byType(BackdropFilter), findsOneWidget); expect(find.byType(BackdropFilter), findsOneWidget);
await tester.pumpWidget(new CupertinoTabBar( await pumpWidgetWithBoilerplate(tester, new CupertinoTabBar(
items: <BottomNavigationBarItem>[ items: <BottomNavigationBarItem>[
const BottomNavigationBarItem( const BottomNavigationBarItem(
icon: const ImageIcon(const TestImageProvider(24, 24)), icon: const ImageIcon(const TestImageProvider(24, 24)),
...@@ -90,7 +100,7 @@ void main() { ...@@ -90,7 +100,7 @@ void main() {
testWidgets('Tap callback', (WidgetTester tester) async { testWidgets('Tap callback', (WidgetTester tester) async {
int callbackTab; int callbackTab;
await tester.pumpWidget(new CupertinoTabBar( await pumpWidgetWithBoilerplate(tester, new CupertinoTabBar(
items: <BottomNavigationBarItem>[ items: <BottomNavigationBarItem>[
const BottomNavigationBarItem( const BottomNavigationBarItem(
icon: const ImageIcon(const TestImageProvider(24, 24)), icon: const ImageIcon(const TestImageProvider(24, 24)),
......
...@@ -67,7 +67,10 @@ void main() { ...@@ -67,7 +67,10 @@ void main() {
testWidgets('About box logic defaults to executable name for app name', (WidgetTester tester) async { testWidgets('About box logic defaults to executable name for app name', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
const Material(child: const AboutListTile()), const Directionality(
textDirection: TextDirection.ltr,
child: const Material(child: const AboutListTile()),
),
); );
expect(find.text('About flutter_tester'), findsOneWidget); expect(find.text('About flutter_tester'), findsOneWidget);
}); });
......
...@@ -8,31 +8,34 @@ import 'package:flutter/rendering.dart'; ...@@ -8,31 +8,34 @@ import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
Widget buildSliverAppBarApp({ bool floating, bool pinned, double expandedHeight, bool snap: false }) { Widget buildSliverAppBarApp({ bool floating, bool pinned, double expandedHeight, bool snap: false }) {
return new MediaQuery( return new Directionality(
data: const MediaQueryData(), textDirection: TextDirection.ltr,
child: new Scaffold( child: new MediaQuery(
body: new DefaultTabController( data: const MediaQueryData(),
length: 3, child: new Scaffold(
child: new CustomScrollView( body: new DefaultTabController(
primary: true, length: 3,
slivers: <Widget>[ child: new CustomScrollView(
new SliverAppBar( primary: true,
title: const Text('AppBar Title'), slivers: <Widget>[
floating: floating, new SliverAppBar(
pinned: pinned, title: const Text('AppBar Title'),
expandedHeight: expandedHeight, floating: floating,
snap: snap, pinned: pinned,
bottom: new TabBar( expandedHeight: expandedHeight,
tabs: <String>['A','B','C'].map((String t) => new Tab(text: 'TAB $t')).toList(), snap: snap,
bottom: new TabBar(
tabs: <String>['A','B','C'].map((String t) => new Tab(text: 'TAB $t')).toList(),
),
), ),
), new SliverToBoxAdapter(
new SliverToBoxAdapter( child: new Container(
child: new Container( height: 1200.0,
height: 1200.0, color: Colors.orange[400],
color: Colors.orange[400], ),
), ),
), ],
], ),
), ),
), ),
), ),
...@@ -699,11 +702,14 @@ void main() { ...@@ -699,11 +702,14 @@ void main() {
const MediaQueryData topPadding100 = const MediaQueryData(padding: const EdgeInsets.only(top: 100.0)); const MediaQueryData topPadding100 = const MediaQueryData(padding: const EdgeInsets.only(top: 100.0));
await tester.pumpWidget( await tester.pumpWidget(
new MediaQuery( new Directionality(
data: topPadding100, textDirection: TextDirection.ltr,
child: new Scaffold( child: new MediaQuery(
primary: false, data: topPadding100,
appBar: new AppBar(), child: new Scaffold(
primary: false,
appBar: new AppBar(),
),
), ),
), ),
); );
...@@ -711,11 +717,14 @@ void main() { ...@@ -711,11 +717,14 @@ void main() {
expect(appBarHeight(tester), kToolbarHeight); expect(appBarHeight(tester), kToolbarHeight);
await tester.pumpWidget( await tester.pumpWidget(
new MediaQuery( new Directionality(
data: topPadding100, textDirection: TextDirection.ltr,
child: new Scaffold( child: new MediaQuery(
primary: true, data: topPadding100,
appBar: new AppBar(title: const Text('title')) child: new Scaffold(
primary: true,
appBar: new AppBar(title: const Text('title'))
),
), ),
), ),
); );
...@@ -724,14 +733,17 @@ void main() { ...@@ -724,14 +733,17 @@ void main() {
expect(appBarHeight(tester), kToolbarHeight + 100.0); expect(appBarHeight(tester), kToolbarHeight + 100.0);
await tester.pumpWidget( await tester.pumpWidget(
new MediaQuery( new Directionality(
data: topPadding100, textDirection: TextDirection.ltr,
child: new Scaffold( child: new MediaQuery(
primary: false, data: topPadding100,
appBar: new AppBar( child: new Scaffold(
bottom: new PreferredSize( primary: false,
preferredSize: const Size.fromHeight(200.0), appBar: new AppBar(
child: new Container(), bottom: new PreferredSize(
preferredSize: const Size.fromHeight(200.0),
child: new Container(),
),
), ),
), ),
), ),
...@@ -741,14 +753,17 @@ void main() { ...@@ -741,14 +753,17 @@ void main() {
expect(appBarHeight(tester), kToolbarHeight + 200.0); expect(appBarHeight(tester), kToolbarHeight + 200.0);
await tester.pumpWidget( await tester.pumpWidget(
new MediaQuery( new Directionality(
data: topPadding100, textDirection: TextDirection.ltr,
child: new Scaffold( child: new MediaQuery(
primary: true, data: topPadding100,
appBar: new AppBar( child: new Scaffold(
bottom: new PreferredSize( primary: true,
preferredSize: const Size.fromHeight(200.0), appBar: new AppBar(
child: new Container(), bottom: new PreferredSize(
preferredSize: const Size.fromHeight(200.0),
child: new Container(),
),
), ),
), ),
), ),
...@@ -758,11 +773,14 @@ void main() { ...@@ -758,11 +773,14 @@ void main() {
expect(appBarHeight(tester), kToolbarHeight + 100.0 + 200.0); expect(appBarHeight(tester), kToolbarHeight + 100.0 + 200.0);
await tester.pumpWidget( await tester.pumpWidget(
new MediaQuery( new Directionality(
data: topPadding100, textDirection: TextDirection.ltr,
child: new AppBar( child: new MediaQuery(
primary: false, data: topPadding100,
title: const Text('title'), child: new AppBar(
primary: false,
title: const Text('title'),
),
), ),
), ),
); );
......
...@@ -6,7 +6,12 @@ import 'package:flutter/material.dart'; ...@@ -6,7 +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('ButtonBar default control', (WidgetTester tester) async { testWidgets('ButtonBar default control smoketest', (WidgetTester tester) async {
await tester.pumpWidget(const Center(child: const ButtonBar())); await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: const ButtonBar(),
),
);
}); });
} }
...@@ -107,29 +107,31 @@ void main() { ...@@ -107,29 +107,31 @@ void main() {
feedback.dispose(); feedback.dispose();
}); });
testWidgets( testWidgets('Chip does not constrain size of label widget if it does not exceed '
'Chip does not constrain size of label widget if it does not exceed ' 'the available space', (WidgetTester tester) async {
'the available space', (WidgetTester tester) async {
const double labelWidth = 50.0; const double labelWidth = 50.0;
const double labelHeight = 30.0; const double labelHeight = 30.0;
final Key labelKey = new UniqueKey(); final Key labelKey = new UniqueKey();
await tester.pumpWidget( await tester.pumpWidget(
new Material( new Directionality(
child: new Center( textDirection: TextDirection.ltr,
child: new Container( child: new Material(
width: 500.0, child: new Center(
height: 500.0, child: new Container(
child: new Column( width: 500.0,
children: <Widget>[ height: 500.0,
new Chip( child: new Column(
label: new Container( children: <Widget>[
key: labelKey, new Chip(
width: labelWidth, label: new Container(
height: labelHeight, key: labelKey,
width: labelWidth,
height: labelHeight,
),
), ),
), ],
], ),
), ),
), ),
), ),
...@@ -141,15 +143,13 @@ void main() { ...@@ -141,15 +143,13 @@ void main() {
expect(labelSize.height, labelHeight); expect(labelSize.height, labelHeight);
}); });
testWidgets( testWidgets('Chip constrains the size of the label widget when it exceeds the '
'Chip constrains the size of the label widget when it exceeds the ' 'available space', (WidgetTester tester) async {
'available space', (WidgetTester tester) async {
await _testConstrainedLabel(tester); await _testConstrainedLabel(tester);
}); });
testWidgets( testWidgets('Chip constrains the size of the label widget when it exceeds the '
'Chip constrains the size of the label widget when it exceeds the ' 'available space and the avatar is present', (WidgetTester tester) async {
'available space and the avatar is present', (WidgetTester tester) async {
await _testConstrainedLabel( await _testConstrainedLabel(
tester, tester,
avatar: const CircleAvatar( avatar: const CircleAvatar(
...@@ -158,20 +158,16 @@ void main() { ...@@ -158,20 +158,16 @@ void main() {
); );
}); });
testWidgets( testWidgets('Chip constrains the size of the label widget when it exceeds the '
'Chip constrains the size of the label widget when it exceeds the ' 'available space and the delete icon is present', (WidgetTester tester) async {
'available space and the delete icon is present',
(WidgetTester tester) async {
await _testConstrainedLabel( await _testConstrainedLabel(
tester, tester,
onDeleted: () {}, onDeleted: () {},
); );
}); });
testWidgets( testWidgets('Chip constrains the size of the label widget when it exceeds the '
'Chip constrains the size of the label widget when it exceeds the ' 'available space and both avatar and delete icons are present', (WidgetTester tester) async {
'available space and both avatar and delete icons are present',
(WidgetTester tester) async {
await _testConstrainedLabel( await _testConstrainedLabel(
tester, tester,
avatar: const CircleAvatar( avatar: const CircleAvatar(
...@@ -223,4 +219,104 @@ void main() { ...@@ -223,4 +219,104 @@ void main() {
expect(tester.getSize(find.byType(Text)), const Size(40.0, 10.0)); expect(tester.getSize(find.byType(Text)), const Size(40.0, 10.0));
expect(tester.getSize(find.byType(Chip)), const Size(800.0, 32.0)); expect(tester.getSize(find.byType(Chip)), const Size(800.0, 32.0));
}); });
testWidgets('Chip supports RTL', (WidgetTester tester) async {
final Widget test = new Overlay(
initialEntries: <OverlayEntry>[
new OverlayEntry(
builder: (BuildContext context) {
return new Material(
child: new Center(
child: new Chip(
onDeleted: () { },
label: new Text('ABC'),
),
),
);
},
),
],
);
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.rtl,
child: test,
),
);
expect(tester.getCenter(find.text('ABC')).dx, greaterThan(tester.getCenter(find.byType(Icon)).dx));
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: test,
),
);
expect(tester.getCenter(find.text('ABC')).dx, lessThan(tester.getCenter(find.byType(Icon)).dx));
});
testWidgets('Chip padding - LTR', (WidgetTester tester) async {
final GlobalKey keyA = new GlobalKey();
final GlobalKey keyB = new GlobalKey();
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Overlay(
initialEntries: <OverlayEntry>[
new OverlayEntry(
builder: (BuildContext context) {
return new Material(
child: new Center(
child: new Chip(
avatar: new Placeholder(key: keyA),
label: new Placeholder(key: keyB),
onDeleted: () { },
),
),
);
},
),
],
),
),
);
expect(tester.getTopLeft(find.byKey(keyA)), const Offset(0.0, 284.0));
expect(tester.getBottomRight(find.byKey(keyA)), const Offset(32.0, 316.0));
expect(tester.getTopLeft(find.byKey(keyB)), const Offset(40.0, 284.0));
expect(tester.getBottomRight(find.byKey(keyB)), const Offset(774.0, 316.0));
expect(tester.getTopLeft(find.byType(Icon)), const Offset(778.0, 291.0));
expect(tester.getBottomRight(find.byType(Icon)), const Offset(796.0, 309.0));
});
testWidgets('Chip padding - RTL', (WidgetTester tester) async {
final GlobalKey keyA = new GlobalKey();
final GlobalKey keyB = new GlobalKey();
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.rtl,
child: new Overlay(
initialEntries: <OverlayEntry>[
new OverlayEntry(
builder: (BuildContext context) {
return new Material(
child: new Center(
child: new Chip(
avatar: new Placeholder(key: keyA),
label: new Placeholder(key: keyB),
onDeleted: () { },
),
),
);
},
),
],
),
),
);
expect(tester.getTopRight(find.byKey(keyA)), const Offset(800.0 - 0.0, 284.0));
expect(tester.getBottomLeft(find.byKey(keyA)), const Offset(800.0 - 32.0, 316.0));
expect(tester.getTopRight(find.byKey(keyB)), const Offset(800.0 - 40.0, 284.0));
expect(tester.getBottomLeft(find.byKey(keyB)), const Offset(800.0 - 774.0, 316.0));
expect(tester.getTopRight(find.byType(Icon)), const Offset(800.0 - 778.0, 291.0));
expect(tester.getBottomLeft(find.byType(Icon)), const Offset(800.0 - 796.0, 309.0));
});
} }
...@@ -10,10 +10,17 @@ import 'package:flutter/rendering.dart'; ...@@ -10,10 +10,17 @@ import 'package:flutter/rendering.dart';
import '../widgets/semantics_tester.dart'; import '../widgets/semantics_tester.dart';
Widget wrap({ Widget child }) {
return new Directionality(
textDirection: TextDirection.ltr,
child: new Material(child: child),
);
}
void main() { void main() {
testWidgets('CheckboxListTile control test', (WidgetTester tester) async { testWidgets('CheckboxListTile control test', (WidgetTester tester) async {
final List<dynamic> log = <dynamic>[]; final List<dynamic> log = <dynamic>[];
await tester.pumpWidget(new Material( await tester.pumpWidget(wrap(
child: new CheckboxListTile( child: new CheckboxListTile(
value: true, value: true,
onChanged: (bool value) { log.add(value); }, onChanged: (bool value) { log.add(value); },
...@@ -28,7 +35,7 @@ void main() { ...@@ -28,7 +35,7 @@ void main() {
testWidgets('RadioListTile control test', (WidgetTester tester) async { testWidgets('RadioListTile control test', (WidgetTester tester) async {
final List<dynamic> log = <dynamic>[]; final List<dynamic> log = <dynamic>[];
await tester.pumpWidget(new Material( await tester.pumpWidget(wrap(
child: new RadioListTile<bool>( child: new RadioListTile<bool>(
value: true, value: true,
groupValue: false, groupValue: false,
...@@ -44,7 +51,7 @@ void main() { ...@@ -44,7 +51,7 @@ void main() {
testWidgets('SwitchListTile control test', (WidgetTester tester) async { testWidgets('SwitchListTile control test', (WidgetTester tester) async {
final List<dynamic> log = <dynamic>[]; final List<dynamic> log = <dynamic>[];
await tester.pumpWidget(new Material( await tester.pumpWidget(wrap(
child: new SwitchListTile( child: new SwitchListTile(
value: true, value: true,
onChanged: (bool value) { log.add(value); }, onChanged: (bool value) { log.add(value); },
...@@ -59,7 +66,7 @@ void main() { ...@@ -59,7 +66,7 @@ void main() {
testWidgets('SwitchListTile control test', (WidgetTester tester) async { testWidgets('SwitchListTile control test', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester); final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(new Material( await tester.pumpWidget(wrap(
child: new Column( child: new Column(
children: <Widget>[ children: <Widget>[
new SwitchListTile( new SwitchListTile(
......
...@@ -11,22 +11,24 @@ void main() { ...@@ -11,22 +11,24 @@ void main() {
bool isExpanded; bool isExpanded;
await tester.pumpWidget( await tester.pumpWidget(
new SingleChildScrollView( new MaterialApp(
child: new ExpansionPanelList( home: new SingleChildScrollView(
expansionCallback: (int _index, bool _isExpanded) { child: new ExpansionPanelList(
index = _index; expansionCallback: (int _index, bool _isExpanded) {
isExpanded = _isExpanded; index = _index;
}, isExpanded = _isExpanded;
children: <ExpansionPanel>[ },
new ExpansionPanel( children: <ExpansionPanel>[
headerBuilder: (BuildContext context, bool isExpanded) { new ExpansionPanel(
return new Text(isExpanded ? 'B' : 'A'); headerBuilder: (BuildContext context, bool isExpanded) {
}, return new Text(isExpanded ? 'B' : 'A');
body: const SizedBox(height: 100.0) },
) body: const SizedBox(height: 100.0),
] ),
) ],
) ),
),
),
); );
expect(find.text('A'), findsOneWidget); expect(find.text('A'), findsOneWidget);
...@@ -42,28 +44,28 @@ void main() { ...@@ -42,28 +44,28 @@ void main() {
// now expand the child panel // now expand the child panel
await tester.pumpWidget( await tester.pumpWidget(
new SingleChildScrollView( new MaterialApp(
child: new ExpansionPanelList( home: new SingleChildScrollView(
expansionCallback: (int _index, bool _isExpanded) { child: new ExpansionPanelList(
index = _index; expansionCallback: (int _index, bool _isExpanded) {
isExpanded = _isExpanded; index = _index;
}, isExpanded = _isExpanded;
children: <ExpansionPanel>[ },
new ExpansionPanel( children: <ExpansionPanel>[
headerBuilder: (BuildContext context, bool isExpanded) { new ExpansionPanel(
return new Text(isExpanded ? 'B' : 'A'); headerBuilder: (BuildContext context, bool isExpanded) {
}, return new Text(isExpanded ? 'B' : 'A');
body: const SizedBox(height: 100.0), },
isExpanded: true // this is the addition body: const SizedBox(height: 100.0),
) isExpanded: true, // this is the addition
] ),
) ],
) ),
),
),
); );
await tester.pump(const Duration(milliseconds: 200)); await tester.pump(const Duration(milliseconds: 200));
expect(find.text('A'), findsNothing); expect(find.text('A'), findsNothing);
expect(find.text('B'), findsOneWidget); expect(find.text('B'), findsOneWidget);
box = tester.renderObject(find.byType(ExpansionPanelList)); box = tester.renderObject(find.byType(ExpansionPanelList));
......
...@@ -10,23 +10,25 @@ void main() { ...@@ -10,23 +10,25 @@ void main() {
final Key headerKey = new UniqueKey(); final Key headerKey = new UniqueKey();
final Key footerKey = new UniqueKey(); final Key footerKey = new UniqueKey();
await tester.pumpWidget(new GridTile( await tester.pumpWidget(new MaterialApp(
header: new GridTileBar( home: new GridTile(
key: headerKey, header: new GridTileBar(
leading: const Icon(Icons.thumb_up), key: headerKey,
title: const Text('Header'), leading: const Icon(Icons.thumb_up),
subtitle: const Text('Subtitle'), title: const Text('Header'),
trailing: const Icon(Icons.thumb_up), subtitle: const Text('Subtitle'),
), trailing: const Icon(Icons.thumb_up),
child: new DecoratedBox( ),
decoration: new BoxDecoration( child: new DecoratedBox(
color: Colors.green[500], decoration: new BoxDecoration(
color: Colors.green[500],
),
),
footer: new GridTileBar(
key: footerKey,
title: const Text('Footer'),
backgroundColor: Colors.black38,
), ),
),
footer: new GridTileBar(
key: footerKey,
title: const Text('Footer'),
backgroundColor: Colors.black38,
), ),
)); ));
......
...@@ -98,15 +98,18 @@ void main() { ...@@ -98,15 +98,18 @@ void main() {
'test default icon buttons can be stretched if specified', 'test default icon buttons can be stretched if specified',
(WidgetTester tester) async { (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
new Material( new Directionality(
child: new Row( textDirection: TextDirection.ltr,
crossAxisAlignment: CrossAxisAlignment.stretch, child: new Material(
children: <Widget> [ child: new Row(
new IconButton( crossAxisAlignment: CrossAxisAlignment.stretch,
onPressed: mockOnPressedFunction, children: <Widget> [
icon: const Icon(Icons.ac_unit), new IconButton(
), onPressed: mockOnPressedFunction,
], icon: const Icon(Icons.ac_unit),
),
],
),
), ),
), ),
); );
......
...@@ -9,37 +9,43 @@ void main() { ...@@ -9,37 +9,43 @@ void main() {
testWidgets('InputDecorator always expands horizontally', (WidgetTester tester) async { testWidgets('InputDecorator always expands horizontally', (WidgetTester tester) async {
final Key key = new UniqueKey(); final Key key = new UniqueKey();
await tester.pumpWidget(new Material( await tester.pumpWidget(new MaterialApp(
child: new Center( home: new Material(
child: new InputDecorator( child: new Center(
decoration: const InputDecoration(), child: new InputDecorator(
child: new Container(key: key, width: 50.0, height: 60.0, color: Colors.blue), decoration: const InputDecoration(),
child: new Container(key: key, width: 50.0, height: 60.0, color: Colors.blue),
),
), ),
), ),
)); ));
expect(tester.element(find.byKey(key)).size, equals(const Size(800.0, 60.0))); expect(tester.element(find.byKey(key)).size, equals(const Size(800.0, 60.0)));
await tester.pumpWidget(new Material( await tester.pumpWidget(new MaterialApp(
child: new Center( home: new Material(
child: new InputDecorator( child: new Center(
decoration: const InputDecoration( child: new InputDecorator(
icon: const Icon(Icons.add_shopping_cart), decoration: const InputDecoration(
icon: const Icon(Icons.add_shopping_cart),
),
child: new Container(key: key, width: 50.0, height: 60.0, color: Colors.blue),
), ),
child: new Container(key: key, width: 50.0, height: 60.0, color: Colors.blue),
), ),
), ),
)); ));
expect(tester.element(find.byKey(key)).size, equals(const Size(752.0, 60.0))); expect(tester.element(find.byKey(key)).size, equals(const Size(752.0, 60.0)));
await tester.pumpWidget(new Material( await tester.pumpWidget(new MaterialApp(
child: new Center( home: new Material(
child: new InputDecorator( child: new Center(
decoration: const InputDecoration.collapsed( child: new InputDecorator(
hintText: 'Hint text', decoration: const InputDecoration.collapsed(
hintText: 'Hint text',
),
child: new Container(key: key, width: 50.0, height: 60.0, color: Colors.blue),
), ),
child: new Container(key: key, width: 50.0, height: 60.0, color: Colors.blue),
), ),
), ),
)); ));
......
...@@ -9,32 +9,35 @@ const Color kSelectedColor = const Color(0xFF00FF00); ...@@ -9,32 +9,35 @@ const Color kSelectedColor = const Color(0xFF00FF00);
const Color kUnselectedColor = Colors.transparent; const Color kUnselectedColor = Colors.transparent;
Widget buildFrame(TabController tabController, { Color color, Color selectedColor, double indicatorSize: 12.0 }) { Widget buildFrame(TabController tabController, { Color color, Color selectedColor, double indicatorSize: 12.0 }) {
return new Theme( return new Directionality(
data: new ThemeData(accentColor: kSelectedColor), textDirection: TextDirection.ltr,
child: new SizedBox.expand( child: new Theme(
child: new Center( data: new ThemeData(accentColor: kSelectedColor),
child: new SizedBox( child: new SizedBox.expand(
width: 400.0, child: new Center(
height: 400.0, child: new SizedBox(
child: new Column( width: 400.0,
children: <Widget>[ height: 400.0,
new TabPageSelector( child: new Column(
controller: tabController, children: <Widget>[
color: color, new TabPageSelector(
selectedColor: selectedColor,
indicatorSize: indicatorSize,
),
new Flexible(
child: new TabBarView(
controller: tabController, controller: tabController,
children: <Widget>[ color: color,
const Center(child: const Text('0')), selectedColor: selectedColor,
const Center(child: const Text('1')), indicatorSize: indicatorSize,
const Center(child: const Text('2')),
],
), ),
), new Flexible(
], child: new TabBarView(
controller: tabController,
children: <Widget>[
const Center(child: const Text('0')),
const Center(child: const Text('1')),
const Center(child: const Text('2')),
],
),
),
],
),
), ),
), ),
), ),
......
...@@ -14,6 +14,15 @@ import '../rendering/mock_canvas.dart'; ...@@ -14,6 +14,15 @@ import '../rendering/mock_canvas.dart';
import '../rendering/recording_canvas.dart'; import '../rendering/recording_canvas.dart';
import '../widgets/semantics_tester.dart'; import '../widgets/semantics_tester.dart';
Widget boilerplate({ Widget child }) {
return new Directionality(
textDirection: TextDirection.ltr,
child: new Material(
child: child,
),
);
}
class StateMarker extends StatefulWidget { class StateMarker extends StatefulWidget {
const StateMarker({ Key key, this.child }) : super(key: key); const StateMarker({ Key key, this.child }) : super(key: key);
...@@ -41,7 +50,7 @@ Widget buildFrame({ ...@@ -41,7 +50,7 @@ Widget buildFrame({
bool isScrollable: false, bool isScrollable: false,
Color indicatorColor, Color indicatorColor,
}) { }) {
return new Material( return boilerplate(
child: new DefaultTabController( child: new DefaultTabController(
initialIndex: tabs.indexOf(value), initialIndex: tabs.indexOf(value),
length: tabs.length, length: tabs.length,
...@@ -253,7 +262,7 @@ void main() { ...@@ -253,7 +262,7 @@ void main() {
String value = tabs[0]; String value = tabs[0];
Widget builder() { Widget builder() {
return new Material( return boilerplate(
child: new DefaultTabController( child: new DefaultTabController(
initialIndex: tabs.indexOf(value), initialIndex: tabs.indexOf(value),
length: tabs.length, length: tabs.length,
...@@ -633,7 +642,7 @@ void main() { ...@@ -633,7 +642,7 @@ void main() {
Color secondColor; Color secondColor;
await tester.pumpWidget( await tester.pumpWidget(
new Material( boilerplate(
child: new TabBar( child: new TabBar(
controller: controller, controller: controller,
labelColor: Colors.green[500], labelColor: Colors.green[500],
...@@ -667,7 +676,7 @@ void main() { ...@@ -667,7 +676,7 @@ void main() {
); );
await tester.pumpWidget( await tester.pumpWidget(
new Material( boilerplate(
child: new TabBarView( child: new TabBarView(
controller: controller, controller: controller,
children: <Widget>[ const Text('First'), const Text('Second') ], children: <Widget>[ const Text('First'), const Text('Second') ],
...@@ -765,7 +774,7 @@ void main() { ...@@ -765,7 +774,7 @@ void main() {
); );
Widget buildFrame() { Widget buildFrame() {
return new Material( return boilerplate(
child: new TabBar( child: new TabBar(
key: new UniqueKey(), key: new UniqueKey(),
controller: controller, controller: controller,
...@@ -893,7 +902,7 @@ void main() { ...@@ -893,7 +902,7 @@ void main() {
); );
await tester.pumpWidget( await tester.pumpWidget(
new Material( boilerplate(
child: new TabBar( child: new TabBar(
isScrollable: true, isScrollable: true,
controller: controller, controller: controller,
...@@ -927,7 +936,7 @@ void main() { ...@@ -927,7 +936,7 @@ void main() {
); );
await tester.pumpWidget( await tester.pumpWidget(
new Material( boilerplate(
child: new Column( child: new Column(
children: <Widget>[ children: <Widget>[
new TabBar( new TabBar(
...@@ -985,7 +994,7 @@ void main() { ...@@ -985,7 +994,7 @@ void main() {
); );
await tester.pumpWidget( await tester.pumpWidget(
new Material( boilerplate(
child: new Semantics( child: new Semantics(
container: true, container: true,
child: new TabBar( child: new TabBar(
...@@ -1034,7 +1043,7 @@ void main() { ...@@ -1034,7 +1043,7 @@ void main() {
); );
await tester.pumpWidget( await tester.pumpWidget(
new Material( boilerplate(
child: new Column( child: new Column(
children: <Widget>[ children: <Widget>[
new TabBar( new TabBar(
...@@ -1074,7 +1083,7 @@ void main() { ...@@ -1074,7 +1083,7 @@ void main() {
); );
await tester.pumpWidget( await tester.pumpWidget(
new Material( boilerplate(
child: new Column( child: new Column(
children: <Widget>[ children: <Widget>[
new TabBar( new TabBar(
...@@ -1129,7 +1138,7 @@ void main() { ...@@ -1129,7 +1138,7 @@ void main() {
); );
await tester.pumpWidget( await tester.pumpWidget(
new Material( boilerplate(
child: new Column( child: new Column(
children: <Widget>[ children: <Widget>[
new TabBar( new TabBar(
......
...@@ -167,7 +167,7 @@ void main() { ...@@ -167,7 +167,7 @@ void main() {
], ],
).toString(), ).toString(),
equals( equals(
'LinearGradient(FractionalOffset(0.0, 0.0), FractionalOffset(0.0, 1.0), [Color(0x33333333), Color(0x66666666)], null, TileMode.clamp)', 'LinearGradient(FractionalOffset.topLeft, FractionalOffset.bottomLeft, [Color(0x33333333), Color(0x66666666)], null, TileMode.clamp)',
), ),
); );
}); });
......
...@@ -36,7 +36,7 @@ void main() { ...@@ -36,7 +36,7 @@ void main() {
' │ parentData: <none>\n' ' │ parentData: <none>\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n' ' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ size: Size(800.0, 600.0)\n' ' │ size: Size(800.0, 600.0)\n'
' │ alignment: FractionalOffset(0.5, 0.5)\n' ' │ alignment: FractionalOffset.center\n'
' │ minWidth: 0.0\n' ' │ minWidth: 0.0\n'
' │ maxWidth: Infinity\n' ' │ maxWidth: Infinity\n'
' │ minHeight: 0.0\n' ' │ minHeight: 0.0\n'
...@@ -122,7 +122,7 @@ void main() { ...@@ -122,7 +122,7 @@ void main() {
' │ parentData: <none>\n' ' │ parentData: <none>\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n' ' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ size: Size(800.0, 600.0)\n' ' │ size: Size(800.0, 600.0)\n'
' │ alignment: FractionalOffset(0.5, 0.5)\n' ' │ alignment: FractionalOffset.center\n'
' │ minWidth: 10.0\n' ' │ minWidth: 10.0\n'
' │ maxWidth: 500.0\n' ' │ maxWidth: 500.0\n'
' │ minHeight: 0.0\n' ' │ minHeight: 0.0\n'
...@@ -158,7 +158,7 @@ void main() { ...@@ -158,7 +158,7 @@ void main() {
' │ parentData: <none>\n' ' │ parentData: <none>\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n' ' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ size: Size(800.0, 600.0)\n' ' │ size: Size(800.0, 600.0)\n'
' │ alignment: FractionalOffset(0.5, 0.5)\n' ' │ alignment: FractionalOffset.center\n'
' │ minWidth: 10.0\n' ' │ minWidth: 10.0\n'
' │ maxWidth: use parent maxWidth constraint\n' ' │ maxWidth: use parent maxWidth constraint\n'
' │ minHeight: use parent minHeight constraint\n' ' │ minHeight: use parent minHeight constraint\n'
......
...@@ -36,7 +36,7 @@ void main() { ...@@ -36,7 +36,7 @@ void main() {
RenderBox child1, child2; RenderBox child1, child2;
bool movedChild1 = false; bool movedChild1 = false;
bool movedChild2 = false; bool movedChild2 = false;
final RenderFlex block = new RenderFlex(); final RenderFlex block = new RenderFlex(textDirection: TextDirection.ltr);
block.add(child1 = new RenderLayoutTestBox(() { movedChild1 = true; })); block.add(child1 = new RenderLayoutTestBox(() { movedChild1 = true; }));
block.add(child2 = new RenderLayoutTestBox(() { movedChild2 = true; })); block.add(child2 = new RenderLayoutTestBox(() { movedChild2 = true; }));
......
...@@ -19,6 +19,7 @@ Widget buildFrame(ScrollPhysics physics) { ...@@ -19,6 +19,7 @@ Widget buildFrame(ScrollPhysics physics) {
height: 650.0, height: 650.0,
child: new Column( child: new Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
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')),
new Expanded(child: new Container()), new Expanded(child: new Container()),
......
...@@ -59,7 +59,8 @@ void main() { ...@@ -59,7 +59,8 @@ void main() {
' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n' ' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n'
' │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)\n' ' │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)\n'
' │ size: Size(63.0, 88.0)\n' ' │ size: Size(63.0, 88.0)\n'
' │ padding: EdgeInsets(5.0, 5.0, 5.0, 5.0)\n' ' │ padding: EdgeInsets.all(5.0)\n'
' │ textDirection: null\n'
' │\n' ' │\n'
' └─child: RenderConstrainedBox#00000 relayoutBoundary=up2\n' ' └─child: RenderConstrainedBox#00000 relayoutBoundary=up2\n'
' │ creator: ConstrainedBox ← Padding ← Container ← Align ← [root]\n' ' │ creator: ConstrainedBox ← Padding ← Container ← Align ← [root]\n'
...@@ -98,7 +99,8 @@ void main() { ...@@ -98,7 +99,8 @@ void main() {
' │ parentData: <none> (can use size)\n' ' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=53.0, h=78.0)\n' ' │ constraints: BoxConstraints(w=53.0, h=78.0)\n'
' │ size: Size(53.0, 78.0)\n' ' │ size: Size(53.0, 78.0)\n'
' │ padding: EdgeInsets(7.0, 7.0, 7.0, 7.0)\n' ' │ padding: EdgeInsets.all(7.0)\n'
' │ textDirection: null\n'
' │\n' ' │\n'
' └─child: RenderPositionedBox#00000\n' ' └─child: RenderPositionedBox#00000\n'
' │ creator: Align ← Padding ← DecoratedBox ← DecoratedBox ←\n' ' │ creator: Align ← Padding ← DecoratedBox ← DecoratedBox ←\n'
...@@ -106,7 +108,7 @@ void main() { ...@@ -106,7 +108,7 @@ void main() {
' │ parentData: offset=Offset(7.0, 7.0) (can use size)\n' ' │ parentData: offset=Offset(7.0, 7.0) (can use size)\n'
' │ constraints: BoxConstraints(w=39.0, h=64.0)\n' ' │ constraints: BoxConstraints(w=39.0, h=64.0)\n'
' │ size: Size(39.0, 64.0)\n' ' │ size: Size(39.0, 64.0)\n'
' │ alignment: FractionalOffset(1.0, 1.0)\n' ' │ alignment: FractionalOffset.bottomRight\n'
' │ widthFactor: expand\n' ' │ widthFactor: expand\n'
' │ heightFactor: expand\n' ' │ heightFactor: expand\n'
' │\n' ' │\n'
......
...@@ -47,6 +47,7 @@ void main() { ...@@ -47,6 +47,7 @@ void main() {
testWidgets('Flexible defaults to loose', (WidgetTester tester) async { testWidgets('Flexible defaults to loose', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
new Row( new Row(
textDirection: TextDirection.ltr,
children: <Widget>[ children: <Widget>[
const Flexible(child: const SizedBox(width: 100.0, height: 200.0)), const Flexible(child: const SizedBox(width: 100.0, height: 200.0)),
], ],
...@@ -60,6 +61,7 @@ void main() { ...@@ -60,6 +61,7 @@ void main() {
testWidgets('Can pass null for flex', (WidgetTester tester) async { testWidgets('Can pass null for flex', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
new Row( new Row(
textDirection: TextDirection.ltr,
children: <Widget>[ children: <Widget>[
const Expanded(flex: null, child: const Text('one')), const Expanded(flex: null, child: const Text('one')),
const Flexible(flex: null, child: const Text('two')), const Flexible(flex: null, child: const Text('two')),
......
...@@ -145,6 +145,7 @@ void main() { ...@@ -145,6 +145,7 @@ void main() {
node: parentFocusScope, node: parentFocusScope,
autofocus: true, autofocus: true,
child: new Row( child: new Row(
textDirection: TextDirection.ltr,
children: <Widget>[ children: <Widget>[
const TestFocusable( const TestFocusable(
no: 'a', no: 'a',
...@@ -193,6 +194,7 @@ void main() { ...@@ -193,6 +194,7 @@ void main() {
new FocusScope( new FocusScope(
node: parentFocusScope, node: parentFocusScope,
child: new Row( child: new Row(
textDirection: TextDirection.ltr,
children: <Widget>[ children: <Widget>[
const TestFocusable( const TestFocusable(
no: 'a', no: 'a',
...@@ -218,6 +220,7 @@ void main() { ...@@ -218,6 +220,7 @@ void main() {
new FocusScope( new FocusScope(
node: parentFocusScope, node: parentFocusScope,
child: new Row( child: new Row(
textDirection: TextDirection.ltr,
children: <Widget>[ children: <Widget>[
const TestFocusable( const TestFocusable(
no: 'a', no: 'a',
......
...@@ -486,7 +486,7 @@ void main() { ...@@ -486,7 +486,7 @@ void main() {
expect( expect(
element.toStringDeep(), element.toStringDeep(),
equalsIgnoringHashCodes( equalsIgnoringHashCodes(
'Column-[GlobalKey#00000](renderObject: RenderFlex#00000)\n' 'Column-[GlobalKey#00000](direction: vertical, mainAxisAlignment: start, crossAxisAlignment: center, renderObject: RenderFlex#00000)\n'
'├Container\n' '├Container\n'
'│└LimitedBox(maxWidth: 0.0, maxHeight: 0.0, renderObject: RenderLimitedBox#00000 relayoutBoundary=up1)\n' '│└LimitedBox(maxWidth: 0.0, maxHeight: 0.0, renderObject: RenderLimitedBox#00000 relayoutBoundary=up1)\n'
'│ └ConstrainedBox(BoxConstraints(biggest), renderObject: RenderConstrainedBox#00000 relayoutBoundary=up2)\n' '│ └ConstrainedBox(BoxConstraints(biggest), renderObject: RenderConstrainedBox#00000 relayoutBoundary=up2)\n'
......
...@@ -24,10 +24,13 @@ void main() { ...@@ -24,10 +24,13 @@ void main() {
}); });
testWidgets('GlobalKey children of two nodes', (WidgetTester tester) async { testWidgets('GlobalKey children of two nodes', (WidgetTester tester) async {
await tester.pumpWidget(new Row(children: <Widget>[ await tester.pumpWidget(new Row(
new Container(child: new Container(key: const GlobalObjectKey(0))), textDirection: TextDirection.ltr,
new Container(child: new Container(key: const GlobalObjectKey(0))), children: <Widget>[
])); new Container(child: new Container(key: const GlobalObjectKey(0))),
new Container(child: new Container(key: const GlobalObjectKey(0))),
],
));
final dynamic error = tester.takeException(); final dynamic error = tester.takeException();
expect(error, isFlutterError); expect(error, isFlutterError);
expect(error.toString(), startsWith('Multiple widgets used the same GlobalKey.\n')); expect(error.toString(), startsWith('Multiple widgets used the same GlobalKey.\n'));
...@@ -38,10 +41,13 @@ void main() { ...@@ -38,10 +41,13 @@ void main() {
}); });
testWidgets('GlobalKey children of two different nodes', (WidgetTester tester) async { testWidgets('GlobalKey children of two different nodes', (WidgetTester tester) async {
await tester.pumpWidget(new Row(children: <Widget>[ await tester.pumpWidget(new Row(
new Container(child: new Container(key: const GlobalObjectKey(0))), textDirection: TextDirection.ltr,
new Container(key: const Key('x'), child: new Container(key: const GlobalObjectKey(0))), children: <Widget>[
])); new Container(child: new Container(key: const GlobalObjectKey(0))),
new Container(key: const Key('x'), child: new Container(key: const GlobalObjectKey(0))),
],
));
final dynamic error = tester.takeException(); final dynamic error = tester.takeException();
expect(error, isFlutterError); expect(error, isFlutterError);
expect(error.toString(), startsWith('Multiple widgets used the same GlobalKey.\n')); expect(error.toString(), startsWith('Multiple widgets used the same GlobalKey.\n'));
...@@ -55,17 +61,20 @@ void main() { ...@@ -55,17 +61,20 @@ void main() {
testWidgets('GlobalKey children of two nodes', (WidgetTester tester) async { testWidgets('GlobalKey children of two nodes', (WidgetTester tester) async {
StateSetter nestedSetState; StateSetter nestedSetState;
bool flag = false; bool flag = false;
await tester.pumpWidget(new Row(children: <Widget>[ await tester.pumpWidget(new Row(
new Container(child: new Container(key: const GlobalObjectKey(0))), textDirection: TextDirection.ltr,
new Container(child: new StatefulBuilder( children: <Widget>[
builder: (BuildContext context, StateSetter setState) { new Container(child: new Container(key: const GlobalObjectKey(0))),
nestedSetState = setState; new Container(child: new StatefulBuilder(
if (flag) builder: (BuildContext context, StateSetter setState) {
return new Container(key: const GlobalObjectKey(0)); nestedSetState = setState;
return new Container(); if (flag)
}, return new Container(key: const GlobalObjectKey(0));
)), return new Container();
])); },
)),
],
));
nestedSetState(() { flag = true; }); nestedSetState(() { flag = true; });
await tester.pump(); await tester.pump();
final dynamic error = tester.takeException(); final dynamic error = tester.takeException();
......
...@@ -235,6 +235,7 @@ void main() { ...@@ -235,6 +235,7 @@ void main() {
// of the Image changes and the MediaQuery widgets do not. // of the Image changes and the MediaQuery widgets do not.
await tester.pumpWidget( await tester.pumpWidget(
new Row( new Row(
textDirection: TextDirection.ltr,
children: <Widget> [ children: <Widget> [
new MediaQuery( new MediaQuery(
key: mediaQueryKey2, key: mediaQueryKey2,
...@@ -263,6 +264,7 @@ void main() { ...@@ -263,6 +264,7 @@ void main() {
await tester.pumpWidget( await tester.pumpWidget(
new Row( new Row(
textDirection: TextDirection.ltr,
children: <Widget> [ children: <Widget> [
new MediaQuery( new MediaQuery(
key: mediaQueryKey2, key: mediaQueryKey2,
......
...@@ -29,6 +29,7 @@ class SizeChangerState extends State<SizeChanger> { ...@@ -29,6 +29,7 @@ class SizeChangerState extends State<SizeChanger> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Row( return new Row(
textDirection: TextDirection.ltr,
children: <Widget>[ children: <Widget>[
new SizedBox( new SizedBox(
height: _flag ? 50.0 : 100.0, height: _flag ? 50.0 : 100.0,
......
...@@ -42,7 +42,7 @@ void main() { ...@@ -42,7 +42,7 @@ void main() {
.where((DiagnosticsNode n) => !n.hidden) .where((DiagnosticsNode n) => !n.hidden)
.map((DiagnosticsNode n) => n.toString()).toList(); .map((DiagnosticsNode n) => n.toString()).toList();
expect(description, <String>[ expect(description, <String>[
'alignment: FractionalOffset(0.5, 0.5)', 'alignment: FractionalOffset.center',
'minWidth: 1.0', 'minWidth: 1.0',
'maxWidth: 2.0', 'maxWidth: 2.0',
'minHeight: 3.0', 'minHeight: 3.0',
......
...@@ -43,7 +43,7 @@ void main() { ...@@ -43,7 +43,7 @@ void main() {
' ╎ │ size)\n' ' ╎ │ size)\n'
' ╎ │ constraints: BoxConstraints(w=800.0, h=600.0)\n' ' ╎ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' ╎ │ size: Size(800.0, 600.0)\n' ' ╎ │ size: Size(800.0, 600.0)\n'
' ╎ │ alignment: FractionalOffset(0.0, 0.0)\n' ' ╎ │ alignment: FractionalOffset.topLeft\n'
' ╎ │ fit: expand\n' ' ╎ │ fit: expand\n'
' ╎ │ overflow: clip\n' ' ╎ │ overflow: clip\n'
' ╎ │\n' ' ╎ │\n'
...@@ -112,7 +112,7 @@ void main() { ...@@ -112,7 +112,7 @@ void main() {
' ╎ │ size)\n' ' ╎ │ size)\n'
' ╎ │ constraints: BoxConstraints(w=800.0, h=600.0)\n' ' ╎ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' ╎ │ size: Size(800.0, 600.0)\n' ' ╎ │ size: Size(800.0, 600.0)\n'
' ╎ │ alignment: FractionalOffset(0.0, 0.0)\n' ' ╎ │ alignment: FractionalOffset.topLeft\n'
' ╎ │ fit: expand\n' ' ╎ │ fit: expand\n'
' ╎ │ overflow: clip\n' ' ╎ │ overflow: clip\n'
' ╎ │\n' ' ╎ │\n'
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment