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.
diff --git a/examples/layers/test/gestures_test.dart b/examples/layers/test/gestures_test.dart
index 3d82e2a..902385a 100644
--- a/examples/layers/test/gestures_test.dart
+++ b/examples/layers/test/gestures_test.dart
@@ -9,7 +9,10 @@
void main() {
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);
MaterialColor getSwatch() => tester.state<GestureDemoState>(finder).swatch;
diff --git a/packages/flutter/lib/src/material/app.dart b/packages/flutter/lib/src/material/app.dart
index efe0ffd..a114622 100644
--- a/packages/flutter/lib/src/material/app.dart
+++ b/packages/flutter/lib/src/material/app.dart
@@ -26,11 +26,14 @@
decorationStyle: TextDecorationStyle.double
);
+// Delegate that fetches the default (English) strings.
class _MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
const _MaterialLocalizationsDelegate();
@override
- Future<MaterialLocalizations> load(Locale locale) => MaterialLocalizations.load(locale);
+ Future<MaterialLocalizations> load(Locale locale) {
+ return new SynchronousFuture<MaterialLocalizations>(const MaterialLocalizations());
+ }
@override
bool shouldReload(_MaterialLocalizationsDelegate old) => false;
@@ -317,8 +320,8 @@
// Combine the Localizations for Material with the ones contributed
// by the localizationsDelegates parameter, if any.
- Iterable<LocalizationsDelegate<dynamic>> _createLocalizationsDelegates() sync* {
- yield const _MaterialLocalizationsDelegate();
+ Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates sync* {
+ yield const _MaterialLocalizationsDelegate(); // TODO(ianh): make this configurable
if (widget.localizationsDelegates != null)
yield* widget.localizationsDelegates;
}
@@ -397,7 +400,7 @@
onGenerateRoute: _onGenerateRoute,
onUnknownRoute: _onUnknownRoute,
locale: widget.locale,
- localizationsDelegates: _createLocalizationsDelegates(),
+ localizationsDelegates: _localizationsDelegates,
showPerformanceOverlay: widget.showPerformanceOverlay,
checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
diff --git a/packages/flutter/lib/src/material/chip.dart b/packages/flutter/lib/src/material/chip.dart
index cb98ad2..e611fa6 100644
--- a/packages/flutter/lib/src/material/chip.dart
+++ b/packages/flutter/lib/src/material/chip.dart
@@ -106,16 +106,16 @@
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
final bool deletable = onDeleted != null;
- double leftPadding = 12.0;
- double rightPadding = 12.0;
+ double startPadding = 12.0;
+ double endPadding = 12.0;
final List<Widget> children = <Widget>[];
if (avatar != null) {
- leftPadding = 0.0;
+ startPadding = 0.0;
children.add(new ExcludeSemantics(
child: new Container(
- margin: const EdgeInsets.only(right: 8.0),
+ margin: const EdgeInsetsDirectional.only(end: 8.0),
width: _kAvatarDiamater,
height: _kAvatarDiamater,
child: avatar,
@@ -131,7 +131,7 @@
));
if (deletable) {
- rightPadding = 0.0;
+ endPadding = 0.0;
children.add(new GestureDetector(
onTap: Feedback.wrapForTap(onDeleted, context),
child: new Tooltip(
@@ -152,7 +152,7 @@
container: true,
child: new Container(
height: _kChipHeight,
- padding: new EdgeInsets.only(left: leftPadding, right: rightPadding),
+ padding: new EdgeInsetsDirectional.only(start: startPadding, end: endPadding),
decoration: new BoxDecoration(
color: backgroundColor ?? Colors.grey.shade300,
borderRadius: new BorderRadius.circular(16.0),
diff --git a/packages/flutter/lib/src/material/material_localizations.dart b/packages/flutter/lib/src/material/material_localizations.dart
index b086084..b13adce 100644
--- a/packages/flutter/lib/src/material/material_localizations.dart
+++ b/packages/flutter/lib/src/material/material_localizations.dart
@@ -2,44 +2,20 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'dart:async';
-
-import 'package:flutter/foundation.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 {
- 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
/// have been translated.
- final Locale locale;
-
- /// Creates an object that provides default localized resource values for the
- /// for the widgets of the material library.
- ///
- /// This method is typically used to create a [DefaultLocalizationsDelegate].
- /// The [MaterialApp] does so by default.
- static Future<MaterialLocalizations> load(Locale locale) {
- return new SynchronousFuture<MaterialLocalizations>(new MaterialLocalizations._(locale));
- }
-
- /// The `MaterialLocalizations` from the closest [Localizations] instance
- /// that encloses the given context.
- ///
- /// This method is just a convenient shorthand for:
- /// `Localizations.of<MaterialLocalizations>(context, MaterialLocalizations)`.
- ///
- /// References to the localized resources defined by this class are typically
- /// written in terms of this method. For example:
- /// ```dart
- /// tooltip: MaterialLocalizations.of(context).backButtonTooltip,
- /// ```
- static MaterialLocalizations of(BuildContext context) {
- return Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);
- }
+ Locale get locale => const Locale('en', 'US');
/// The tooltip for the leading [AppBar] menu (aka 'hamburger') button
String get openAppDrawerTooltip => 'Open navigation menu';
@@ -55,4 +31,20 @@
/// The tooltip for the [MonthPicker]'s "previous month" button.
String get previousMonthTooltip => 'Previous month';
+
+ /// The `MaterialLocalizations` from the closest [Localizations] instance
+ /// that encloses the given context.
+ ///
+ /// This method is just a convenient shorthand for:
+ /// `Localizations.of<MaterialLocalizations>(context, MaterialLocalizations)`.
+ ///
+ /// References to the localized resources defined by this class are typically
+ /// written in terms of this method. For example:
+ ///
+ /// ```dart
+ /// tooltip: MaterialLocalizations.of(context).backButtonTooltip,
+ /// ```
+ static MaterialLocalizations of(BuildContext context) {
+ return Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);
+ }
}
diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart
index 7968a85..76dd2c0 100644
--- a/packages/flutter/lib/src/material/tabs.dart
+++ b/packages/flutter/lib/src/material/tabs.dart
@@ -150,6 +150,8 @@
MainAxisSize mainAxisSize,
MainAxisAlignment mainAxisAlignment,
CrossAxisAlignment crossAxisAlignment,
+ TextDirection textDirection,
+ VerticalDirection verticalDirection,
TextBaseline textBaseline,
@required this.onPerformLayout,
}) : assert(onPerformLayout != null),
@@ -159,6 +161,8 @@
mainAxisSize: mainAxisSize,
mainAxisAlignment: mainAxisAlignment,
crossAxisAlignment: crossAxisAlignment,
+ textDirection: textDirection,
+ verticalDirection: verticalDirection,
textBaseline: textBaseline,
);
@@ -188,6 +192,8 @@
Key key,
MainAxisAlignment mainAxisAlignment,
CrossAxisAlignment crossAxisAlignment,
+ TextDirection textDirection,
+ VerticalDirection verticalDirection: VerticalDirection.down,
List<Widget> children: const <Widget>[],
this.onPerformLayout,
}) : super(
@@ -197,6 +203,8 @@
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
+ textDirection: textDirection,
+ verticalDirection: verticalDirection,
);
final ValueChanged<List<double>> onPerformLayout;
@@ -208,6 +216,8 @@
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: mainAxisSize,
crossAxisAlignment: crossAxisAlignment,
+ textDirection: getEffectiveTextDirection(context),
+ verticalDirection: verticalDirection,
textBaseline: textBaseline,
onPerformLayout: onPerformLayout,
);
diff --git a/packages/flutter/lib/src/painting/basic_types.dart b/packages/flutter/lib/src/painting/basic_types.dart
index 80d0465..aaddbd7 100644
--- a/packages/flutter/lib/src/painting/basic_types.dart
+++ b/packages/flutter/lib/src/painting/basic_types.dart
@@ -90,3 +90,70 @@
/// change in a render object.
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,
+}
diff --git a/packages/flutter/lib/src/painting/edge_insets.dart b/packages/flutter/lib/src/painting/edge_insets.dart
index c7521cd..1a8514a 100644
--- a/packages/flutter/lib/src/painting/edge_insets.dart
+++ b/packages/flutter/lib/src/painting/edge_insets.dart
@@ -8,13 +8,255 @@
import 'basic_types.dart';
-/// The two cardinal directions in two dimensions.
-enum Axis {
- /// Left and right
- horizontal,
+/// Base class for [EdgeInsets] that allows for text-direction aware
+/// resolution.
+///
+/// A property or argument of this type accepts classes created either with [new
+/// EdgeInsets.fromLTRB] and its variants, or [new EdgeInsetsDirectional].
+///
+/// To convert a [EdgeInsetsGeometry] object of indeterminate type into a
+/// [EdgeInsets] object, call the [resolve] method.
+///
+/// See also:
+///
+/// * [Padding], a widget that describes margins using [EdgeInsetsGeometry].
+abstract class EdgeInsetsGeometry {
+ /// Abstract const constructor. This constructor enables subclasses to provide
+ /// const constructors so that they can be used in const expressions.
+ const EdgeInsetsGeometry();
- /// Up and down
- vertical,
+ double get _bottom;
+ double get _end;
+ double get _left;
+ double get _right;
+ double get _start;
+ double get _top;
+
+ /// Whether every dimension is non-negative.
+ bool get isNonNegative {
+ return _left >= 0.0
+ && _right >= 0.0
+ && _start >= 0.0
+ && _end >= 0.0
+ && _top >= 0.0
+ && _bottom >= 0.0;
+ }
+
+ /// The total offset in the vertical direction.
+ double get horizontal => _left + _right + _start + _end;
+
+ /// The total offset in the horizontal direction.
+ double get vertical => _top + _bottom;
+
+ /// The total offset in the given direction.
+ double along(Axis axis) {
+ assert(axis != null);
+ switch (axis) {
+ case Axis.horizontal:
+ return horizontal;
+ case Axis.vertical:
+ return vertical;
+ }
+ return null;
+ }
+
+ /// The size that this [EdgeInsets] would occupy with an empty interior.
+ Size get collapsedSize => new Size(horizontal, vertical);
+
+ /// An [EdgeInsetsGeometry] with top and bottom, left and right, and start and end flipped.
+ EdgeInsetsGeometry get flipped => new _MixedEdgeInsets.fromLRSETB(_right, _left, _end, _start, _bottom, _top);
+
+ /// Returns a new size that is bigger than the given size by the amount of
+ /// inset in the horizontal and vertical directions.
+ ///
+ /// See also:
+ ///
+ /// * [EdgeInsets.inflateRect], to inflate a [Rect] rather than a [Size] (for
+ /// [EdgeInsetsDirectional], requires first calling [resolve] to establish
+ /// how the start and and map to the left or right).
+ /// * [deflateSize], to deflate a [Size] rather than inflating it.
+ Size inflateSize(Size size) {
+ return new Size(size.width + horizontal, size.height + vertical);
+ }
+
+ /// Returns a new size that is smaller than the given size by the amount of
+ /// inset in the horizontal and vertical directions.
+ ///
+ /// If the argument is smaller than [collapsedSize], then the resulting size
+ /// will have negative dimensions.
+ ///
+ /// See also:
+ ///
+ /// * [EdgeInsets.deflateRect], to deflate a [Rect] rather than a [Size]. (for
+ /// [EdgeInsetsDirectional], requires first calling [resolve] to establish
+ /// how the start and and map to the left or right).
+ /// * [inflateSize], to inflate a [Size] rather than deflating it.
+ Size deflateSize(Size size) {
+ return new Size(size.width - horizontal, size.height - vertical);
+ }
+
+ /// Returns the difference between two [EdgeInsetsGeometry] objects.
+ ///
+ /// If you know you are applying this to two [EdgeInsets] or two
+ /// [EdgeInsetsDirectional] objects, consider using the binary infix `-`
+ /// operator instead, which always returns an object of the same type as the
+ /// operands, and is typed accordingly.
+ ///
+ /// If [subtract] is applied to two objects of the same type ([EdgeInsets] or
+ /// [EdgeInsetsDirectional]), an object of that type will be returned (though
+ /// this is not reflected in the type system). Otherwise, an object
+ /// representing a combination of both is returned. That object can be turned
+ /// into a concrete [EdgeInsets] using [resolve].
+ ///
+ /// This method returns the same result as [add] applied to the result of
+ /// negating the argument (using the prefix unary `-` operator or multiplying
+ /// the argument by -1.0 using the `*` operator).
+ EdgeInsetsGeometry subtract(EdgeInsetsGeometry other) {
+ return new _MixedEdgeInsets.fromLRSETB(
+ _left - other._left,
+ _right - other._right,
+ _start - other._start,
+ _end - other._end,
+ _top - other._top,
+ _bottom - other._bottom,
+ );
+ }
+
+ /// Returns the sum of two [EdgeInsetsGeometry] objects.
+ ///
+ /// If you know you are adding two [EdgeInsets] or two [EdgeInsetsDirectional]
+ /// objects, consider using the `+` operator instead, which always returns an
+ /// object of the same type as the operands, and is typed accordingly.
+ ///
+ /// If [add] is applied to two objects of the same type ([EdgeInsets] or
+ /// [EdgeInsetsDirectional]), an object of that type will be returned (though
+ /// this is not reflected in the type system). Otherwise, an object
+ /// representing a combination of both is returned. That object can be turned
+ /// into a concrete [EdgeInsets] using [resolve].
+ EdgeInsetsGeometry add(EdgeInsetsGeometry other) {
+ return new _MixedEdgeInsets.fromLRSETB(
+ _left + other._left,
+ _right + other._right,
+ _start + other._start,
+ _end + other._end,
+ _top + other._top,
+ _bottom + other._bottom,
+ );
+ }
+
+ /// Returns the [EdgeInsetsGeometry] object with each dimension negated.
+ ///
+ /// This is the same as multiplying the object by -1.0.
+ ///
+ /// This operator returns an object of the same type as the operand.
+ EdgeInsetsGeometry operator -();
+
+ /// Scales the [EdgeInsetsGeometry] object in each dimension by the given factor.
+ ///
+ /// This operator returns an object of the same type as the operand.
+ EdgeInsetsGeometry operator *(double other);
+
+ /// Divides the [EdgeInsetsGeometry] object in each dimension by the given factor.
+ ///
+ /// This operator returns an object of the same type as the operand.
+ EdgeInsetsGeometry operator /(double other);
+
+ /// Integer divides the [EdgeInsetsGeometry] object in each dimension by the given factor.
+ ///
+ /// This operator returns an object of the same type as the operand.
+ EdgeInsetsGeometry operator ~/(double other);
+
+ /// Computes the remainder in each dimension by the given factor.
+ ///
+ /// This operator returns an object of the same type as the operand.
+ EdgeInsetsGeometry operator %(double other);
+
+ /// Linearly interpolate between two [EdgeInsetsGeometry] objects.
+ ///
+ /// If either is null, this function interpolates from [EdgeInsets.zero], and
+ /// the result is an object of the same type as the non-null argument.
+ ///
+ /// If [lerp] is applied to two objects of the same type ([EdgeInsets] or
+ /// [EdgeInsetsDirectional]), an object of that type will be returned (though
+ /// this is not reflected in the type system). Otherwise, an object
+ /// representing a combination of both is returned. That object can be turned
+ /// into a concrete [EdgeInsets] using [resolve].
+ static EdgeInsetsGeometry lerp(EdgeInsetsGeometry a, EdgeInsetsGeometry b, double t) {
+ if (a == null && b == null)
+ return null;
+ if (a == null)
+ return b * t;
+ if (b == null)
+ return a * (1.0 - t);
+ if (a is EdgeInsets && b is EdgeInsets)
+ return EdgeInsets.lerp(a, b, t);
+ if (a is EdgeInsetsDirectional && b is EdgeInsetsDirectional)
+ return EdgeInsetsDirectional.lerp(a, b, t);
+ return new _MixedEdgeInsets.fromLRSETB(
+ ui.lerpDouble(a._left, b._left, t),
+ ui.lerpDouble(a._right, b._right, t),
+ ui.lerpDouble(a._start, b._start, t),
+ ui.lerpDouble(a._end, b._end, t),
+ ui.lerpDouble(a._top, b._top, t),
+ ui.lerpDouble(a._bottom, b._bottom, t)
+ );
+ }
+
+ /// Convert this instance into a [EdgeInsets], which uses literal coordinates
+ /// (i.e. the `left` coordinate being explicitly a distance from the left, and
+ /// the `right` coordinate being explicitly a distance from the right).
+ ///
+ /// See also:
+ ///
+ /// * [EdgeInsets], for which this is a no-op (returns itself).
+ /// * [EdgeInsetsDirectional], which flips the horizontal direction
+ /// based on the `direction` argument.
+ EdgeInsets resolve(TextDirection direction);
+
+ @override
+ String toString() {
+ if (_start == 0.0 && _end == 0.0) {
+ if (_left == 0.0 && _right == 0.0 && _top == 0.0 && _bottom == 0.0)
+ return 'EdgeInsets.zero';
+ if (_left == _right && _right == _top && _top == _bottom)
+ return 'EdgeInsets.all(${_left.toStringAsFixed(1)})';
+ return 'EdgeInsets(${_left.toStringAsFixed(1)}, '
+ '${_top.toStringAsFixed(1)}, '
+ '${_right.toStringAsFixed(1)}, '
+ '${_bottom.toStringAsFixed(1)})';
+ }
+ if (_left == 0.0 && _right == 0.0) {
+ return 'EdgeInsetsDirectional(${_start.toStringAsFixed(1)}, '
+ '${_top.toStringAsFixed(1)}, '
+ '${_end.toStringAsFixed(1)}, '
+ '${_bottom.toStringAsFixed(1)})';
+ }
+ return 'EdgeInsets(${_left.toStringAsFixed(1)}, '
+ '${_top.toStringAsFixed(1)}, '
+ '${_right.toStringAsFixed(1)}, '
+ '${_bottom.toStringAsFixed(1)})'
+ ' + '
+ 'EdgeInsetsDirectional(${_start.toStringAsFixed(1)}, '
+ '0.0, '
+ '${_end.toStringAsFixed(1)}, '
+ '0.0)';
+ }
+
+ @override
+ bool operator ==(dynamic other) {
+ if (other is! EdgeInsetsGeometry)
+ return false;
+ final EdgeInsetsGeometry typedOther = other;
+ return _left == typedOther._left
+ && _right == typedOther._right
+ && _start == typedOther._start
+ && _end == typedOther._end
+ && _top == typedOther._top
+ && _bottom == typedOther._bottom;
+ }
+
+ @override
+ int get hashCode => hashValues(_left, _right, _start, _end, _top, _bottom);
}
/// An immutable set of offsets in each of the four cardinal directions.
@@ -46,13 +288,16 @@
///
/// See also:
///
-/// * [Padding], a widget that describes margins using [EdgeInsets].
+/// * [Padding], a widget that accepts [EdgeInsets] to describe its margins.
+/// * [EdgeInsetsDirectional], which (for properties and arguments that accept
+/// the type [EdgeInsetsGeometry]) allows the horizontal insets to be
+/// specified in a [TextDirection]-aware manner.
@immutable
-class EdgeInsets {
+class EdgeInsets extends EdgeInsetsGeometry {
/// Creates insets from offsets from the left, top, right, and bottom.
const EdgeInsets.fromLTRB(this.left, this.top, this.right, this.bottom);
- /// Creates insets where all the offsets are value.
+ /// Creates insets where all the offsets are `value`.
///
/// ## Sample code
///
@@ -108,15 +353,33 @@
/// The offset from the left.
final double left;
+ @override
+ double get _left => left;
+
/// The offset from the top.
final double top;
+ @override
+ double get _top => top;
+
/// The offset from the right.
final double right;
+ @override
+ double get _right => right;
+
/// The offset from the bottom.
final double bottom;
+ @override
+ double get _bottom => bottom;
+
+ @override
+ double get _start => 0.0;
+
+ @override
+ double get _end => 0.0;
+
/// An Offset describing the vector from the top left of a rectangle to the
/// top left of that rectangle inset by this object.
Offset get topLeft => new Offset(left, top);
@@ -133,31 +396,8 @@
/// bottom right of that rectangle inset by this object.
Offset get bottomRight => new Offset(-right, -bottom);
- /// Whether every dimension is non-negative.
- bool get isNonNegative => left >= 0.0 && top >= 0.0 && right >= 0.0 && bottom >= 0.0;
-
- /// The total offset in the vertical direction.
- double get horizontal => left + right;
-
- /// The total offset in the horizontal direction.
- double get vertical => top + bottom;
-
- /// The total offset in the given direction.
- double along(Axis axis) {
- assert(axis != null);
- switch (axis) {
- case Axis.horizontal:
- return horizontal;
- case Axis.vertical:
- return vertical;
- }
- return null;
- }
-
- /// The size that this EdgeInsets would occupy with an empty interior.
- Size get collapsedSize => new Size(horizontal, vertical);
-
- /// An EdgeInsets with top and bottom as well as left and right flipped.
+ /// An [EdgeInsets] with top and bottom as well as left and right flipped.
+ @override
EdgeInsets get flipped => new EdgeInsets.fromLTRB(right, bottom, left, top);
/// Returns a new rect that is bigger than the given rect in each direction by
@@ -191,92 +431,98 @@
return new Rect.fromLTRB(rect.left + left, rect.top + top, rect.right - right, rect.bottom - bottom);
}
- /// Returns a new size that is bigger than the given size by the amount of
- /// inset in the horizontal and vertical directions.
- ///
- /// See also:
- ///
- /// * [inflateRect], to inflate a [Rect] rather than a [Size].
- /// * [deflateSize], to deflate a [Size] rather than inflating it.
- Size inflateSize(Size size) {
- return new Size(size.width + horizontal, size.height + vertical);
+ @override
+ EdgeInsetsGeometry subtract(EdgeInsetsGeometry other) {
+ if (other is EdgeInsets)
+ return this - other;
+ return super.subtract(other);
}
- /// Returns a new size that is smaller than the given size by the amount of
- /// inset in the horizontal and vertical directions.
- ///
- /// If the argument is smaller than [collapsedSize], then the resulting size
- /// will have negative dimensions.
- ///
- /// See also:
- ///
- /// * [deflateRect], to deflate a [Rect] rather than a [Size].
- /// * [inflateSize], to inflate a [Size] rather than deflating it.
- Size deflateSize(Size size) {
- return new Size(size.width - horizontal, size.height - vertical);
+ @override
+ EdgeInsetsGeometry add(EdgeInsetsGeometry other) {
+ if (other is EdgeInsets)
+ return this + other;
+ return super.add(other);
}
- /// Returns the difference between two EdgeInsets.
+ /// Returns the difference between two [EdgeInsets].
EdgeInsets operator -(EdgeInsets other) {
return new EdgeInsets.fromLTRB(
left - other.left,
top - other.top,
right - other.right,
- bottom - other.bottom
+ bottom - other.bottom,
);
}
- /// Returns the sum of two EdgeInsets.
+ /// Returns the sum of two [EdgeInsets].
EdgeInsets operator +(EdgeInsets other) {
return new EdgeInsets.fromLTRB(
left + other.left,
top + other.top,
right + other.right,
- bottom + other.bottom
+ bottom + other.bottom,
);
}
- /// Scales the EdgeInsets in each dimension by the given factor.
+ /// Returns the [EdgeInsets] object with each dimension negated.
+ ///
+ /// This is the same as multiplying the object by -1.0.
+ @override
+ EdgeInsets operator -() {
+ return new EdgeInsets.fromLTRB(
+ -left,
+ -top,
+ -right,
+ -bottom,
+ );
+ }
+
+ /// Scales the [EdgeInsets] in each dimension by the given factor.
+ @override
EdgeInsets operator *(double other) {
return new EdgeInsets.fromLTRB(
left * other,
top * other,
right * other,
- bottom * other
+ bottom * other,
);
}
- /// Divides the EdgeInsets in each dimension by the given factor.
+ /// Divides the [EdgeInsets] in each dimension by the given factor.
+ @override
EdgeInsets operator /(double other) {
return new EdgeInsets.fromLTRB(
left / other,
top / other,
right / other,
- bottom / other
+ bottom / other,
);
}
- /// Integer divides the EdgeInsets in each dimension by the given factor.
+ /// Integer divides the [EdgeInsets] in each dimension by the given factor.
+ @override
EdgeInsets operator ~/(double other) {
return new EdgeInsets.fromLTRB(
(left ~/ other).toDouble(),
(top ~/ other).toDouble(),
(right ~/ other).toDouble(),
- (bottom ~/ other).toDouble()
+ (bottom ~/ other).toDouble(),
);
}
/// Computes the remainder in each dimension by the given factor.
+ @override
EdgeInsets operator %(double other) {
return new EdgeInsets.fromLTRB(
left % other,
top % other,
right % other,
- bottom % other
+ bottom % other,
);
}
- /// Linearly interpolate between two EdgeInsets.
+ /// Linearly interpolate between two [EdgeInsets].
///
/// If either is null, this function interpolates from [EdgeInsets.zero].
static EdgeInsets lerp(EdgeInsets a, EdgeInsets b, double t) {
@@ -290,29 +536,331 @@
ui.lerpDouble(a.left, b.left, t),
ui.lerpDouble(a.top, b.top, t),
ui.lerpDouble(a.right, b.right, t),
- ui.lerpDouble(a.bottom, b.bottom, t)
+ ui.lerpDouble(a.bottom, b.bottom, t),
);
}
- /// An EdgeInsets with zero offsets in each direction.
- static const EdgeInsets zero = const EdgeInsets.all(0.0);
+ /// An [EdgeInsets] with zero offsets in each direction.
+ static const EdgeInsets zero = const EdgeInsets.only();
@override
- bool operator ==(dynamic other) {
- if (identical(this, other))
- return true;
- if (other is! EdgeInsets)
- return false;
- final EdgeInsets typedOther = other;
- return left == typedOther.left &&
- top == typedOther.top &&
- right == typedOther.right &&
- bottom == typedOther.bottom;
+ EdgeInsets resolve(TextDirection direction) => this;
+}
+
+/// An immutable set of offsets in each of the four cardinal directions, but
+/// whose horizontal components are dependent on the writing direction.
+///
+/// This can be used to indicate padding from the left in [TextDirection.ltr]
+/// text and padding from the right in [TextDirection.rtl] text without having
+/// to be aware of the current text direction.
+///
+/// See also:
+///
+/// * [EdgeInsets], a variant that uses physical labels (left and right instead
+/// of start and end).
+class EdgeInsetsDirectional extends EdgeInsetsGeometry {
+ /// Creates insets from offsets from the start, top, end, and bottom.
+ const EdgeInsetsDirectional.fromSTEB(this.start, this.top, this.end, this.bottom);
+
+ /// Creates insets with only the given values non-zero.
+ ///
+ /// ## Sample code
+ ///
+ /// A margin indent of 40 pixels on the leading side:
+ ///
+ /// ```dart
+ /// const EdgeInsetsDirectional.only(start: 40.0)
+ /// ```
+ const EdgeInsetsDirectional.only({
+ this.start: 0.0,
+ this.top: 0.0,
+ this.end: 0.0,
+ this.bottom: 0.0
+ });
+
+ /// The offset from the start side, the side from which the user will start
+ /// reading text.
+ ///
+ /// This value is normalized into an [EdgeInsets.left] or [EdgeInsets.right]
+ /// value by the [resolve] method.
+ final double start;
+
+ @override
+ double get _start => start;
+
+ /// The offset from the top.
+ ///
+ /// This value is passed through to [EdgeInsets.top] unmodified by the
+ /// [resolve] method.
+ final double top;
+
+ @override
+ double get _top => top;
+
+ /// The offset from the end side, the side on which the user ends reading
+ /// text.
+ ///
+ /// This value is normalized into an [EdgeInsets.left] or [EdgeInsets.right]
+ /// value by the [resolve] method.
+ final double end;
+
+ @override
+ double get _end => end;
+
+ /// The offset from the bottom.
+ ///
+ /// This value is passed through to [EdgeInsets.bottom] unmodified by the
+ /// [resolve] method.
+ final double bottom;
+
+ @override
+ double get _bottom => bottom;
+
+ @override
+ double get _left => 0.0;
+
+ @override
+ double get _right => 0.0;
+
+ @override
+ bool get isNonNegative => start >= 0.0 && top >= 0.0 && end >= 0.0 && bottom >= 0.0;
+
+ /// An [EdgeInsetsDirectional] with [top] and [bottom] as well as [start] and [end] flipped.
+ @override
+ EdgeInsetsDirectional get flipped => new EdgeInsetsDirectional.fromSTEB(end, bottom, start, top);
+
+ @override
+ EdgeInsetsGeometry subtract(EdgeInsetsGeometry other) {
+ if (other is EdgeInsetsDirectional)
+ return this - other;
+ return super.subtract(other);
}
@override
- int get hashCode => hashValues(left, top, right, bottom);
+ EdgeInsetsGeometry add(EdgeInsetsGeometry other) {
+ if (other is EdgeInsetsDirectional)
+ return this + other;
+ return super.add(other);
+ }
+
+ /// Returns the difference between two [EdgeInsetsDirectional] objects.
+ EdgeInsetsDirectional operator -(EdgeInsetsDirectional other) {
+ return new EdgeInsetsDirectional.fromSTEB(
+ start - other.start,
+ top - other.top,
+ end - other.end,
+ bottom - other.bottom,
+ );
+ }
+
+ /// Returns the sum of two [EdgeInsetsDirectional] objects.
+ EdgeInsetsDirectional operator +(EdgeInsetsDirectional other) {
+ return new EdgeInsetsDirectional.fromSTEB(
+ start + other.start,
+ top + other.top,
+ end + other.end,
+ bottom + other.bottom,
+ );
+ }
+
+ /// Returns the [EdgeInsetsDirectional] object with each dimension negated.
+ ///
+ /// This is the same as multiplying the object by -1.0.
+ @override
+ EdgeInsetsDirectional operator -() {
+ return new EdgeInsetsDirectional.fromSTEB(
+ -start,
+ -top,
+ -end,
+ -bottom,
+ );
+ }
+
+ /// Scales the [EdgeInsetsDirectional] object in each dimension by the given factor.
+ @override
+ EdgeInsetsDirectional operator *(double other) {
+ return new EdgeInsetsDirectional.fromSTEB(
+ start * other,
+ top * other,
+ end * other,
+ bottom * other,
+ );
+ }
+
+ /// Divides the [EdgeInsetsDirectional] object in each dimension by the given factor.
+ @override
+ EdgeInsetsDirectional operator /(double other) {
+ return new EdgeInsetsDirectional.fromSTEB(
+ start / other,
+ top / other,
+ end / other,
+ bottom / other,
+ );
+ }
+
+ /// Integer divides the [EdgeInsetsDirectional] object in each dimension by the given factor.
+ @override
+ EdgeInsetsDirectional operator ~/(double other) {
+ return new EdgeInsetsDirectional.fromSTEB(
+ (start ~/ other).toDouble(),
+ (top ~/ other).toDouble(),
+ (end ~/ other).toDouble(),
+ (bottom ~/ other).toDouble(),
+ );
+ }
+
+ /// Computes the remainder in each dimension by the given factor.
+ @override
+ EdgeInsetsDirectional operator %(double other) {
+ return new EdgeInsetsDirectional.fromSTEB(
+ start % other,
+ top % other,
+ end % other,
+ bottom % other,
+ );
+ }
+
+ /// Linearly interpolate between two [EdgeInsetsDirectional].
+ ///
+ /// If either is null, this function interpolates from [EdgeInsetsDirectional.zero].
+ ///
+ /// To interpolate between two [EdgeInsetsGeometry] objects of arbitrary type
+ /// (either [EdgeInsets] or [EdgeInsetsDirectional]), consider the
+ /// [EdgeInsetsGeometry.lerp] static method.
+ static EdgeInsetsDirectional lerp(EdgeInsetsDirectional a, EdgeInsetsDirectional b, double t) {
+ if (a == null && b == null)
+ return null;
+ if (a == null)
+ return b * t;
+ if (b == null)
+ return a * (1.0 - t);
+ return new EdgeInsetsDirectional.fromSTEB(
+ ui.lerpDouble(a.start, b.start, t),
+ ui.lerpDouble(a.top, b.top, t),
+ ui.lerpDouble(a.end, b.end, t),
+ ui.lerpDouble(a.bottom, b.bottom, t),
+ );
+ }
+
+ /// An [EdgeInsetsDirectional] with zero offsets in each direction.
+ ///
+ /// Consider using [EdgeInsets.zero] instead, since that object has the same
+ /// effect, but will be cheaper to [resolve].
+ static const EdgeInsetsDirectional zero = const EdgeInsetsDirectional.only();
@override
- String toString() => "EdgeInsets($left, $top, $right, $bottom)";
+ EdgeInsets resolve(TextDirection direction) {
+ assert(direction != null);
+ switch (direction) {
+ case TextDirection.ltr:
+ return new EdgeInsets.fromLTRB(start, top, end, bottom);
+ case TextDirection.rtl:
+ return new EdgeInsets.fromLTRB(end, top, start, bottom);
+ }
+ return null;
+ }
+}
+
+class _MixedEdgeInsets extends EdgeInsetsGeometry {
+ const _MixedEdgeInsets.fromLRSETB(this._left, this._right, this._start, this._end, this._top, this._bottom);
+
+ @override
+ final double _left;
+
+ @override
+ final double _right;
+
+ @override
+ final double _start;
+
+ @override
+ final double _end;
+
+ @override
+ final double _top;
+
+ @override
+ final double _bottom;
+
+ @override
+ bool get isNonNegative {
+ return _left >= 0.0
+ && _right >= 0.0
+ && _start >= 0.0
+ && _end >= 0.0
+ && _top >= 0.0
+ && _bottom >= 0.0;
+ }
+
+ @override
+ _MixedEdgeInsets operator -() {
+ return new _MixedEdgeInsets.fromLRSETB(
+ -_left,
+ -_right,
+ -_start,
+ -_end,
+ -_top,
+ -_bottom,
+ );
+ }
+
+ @override
+ _MixedEdgeInsets operator *(double other) {
+ return new _MixedEdgeInsets.fromLRSETB(
+ _left * other,
+ _right * other,
+ _start * other,
+ _end * other,
+ _top * other,
+ _bottom * other,
+ );
+ }
+
+ @override
+ _MixedEdgeInsets operator /(double other) {
+ return new _MixedEdgeInsets.fromLRSETB(
+ _left / other,
+ _right / other,
+ _start / other,
+ _end / other,
+ _top / other,
+ _bottom / other,
+ );
+ }
+
+ @override
+ _MixedEdgeInsets operator ~/(double other) {
+ return new _MixedEdgeInsets.fromLRSETB(
+ (_left ~/ other).toDouble(),
+ (_right ~/ other).toDouble(),
+ (_start ~/ other).toDouble(),
+ (_end ~/ other).toDouble(),
+ (_top ~/ other).toDouble(),
+ (_bottom ~/ other).toDouble(),
+ );
+ }
+
+ @override
+ _MixedEdgeInsets operator %(double other) {
+ return new _MixedEdgeInsets.fromLRSETB(
+ _left % other,
+ _right % other,
+ _start % other,
+ _end % other,
+ _top % other,
+ _bottom % other,
+ );
+ }
+
+ @override
+ EdgeInsets resolve(TextDirection direction) {
+ assert(direction != null);
+ switch (direction) {
+ case TextDirection.ltr:
+ return new EdgeInsets.fromLTRB(_start + _left, _top, _end + _right, _bottom);
+ case TextDirection.rtl:
+ return new EdgeInsets.fromLTRB(_end + _left, _top, _start + _left, _bottom);
+ }
+ return null;
+ }
}
diff --git a/packages/flutter/lib/src/painting/fractional_offset.dart b/packages/flutter/lib/src/painting/fractional_offset.dart
index 90b4549..76c140d 100644
--- a/packages/flutter/lib/src/painting/fractional_offset.dart
+++ b/packages/flutter/lib/src/painting/fractional_offset.dart
@@ -8,6 +8,212 @@
import 'basic_types.dart';
+/// Base class for [FractionalOffset] that allows for text-direction aware
+/// resolution.
+///
+/// A property or argument of this type accepts classes created either with [new
+/// FractionalOffset] and its variants, or [new FractionalOffsetDirectional].
+///
+/// To convert a [FractionalOffsetGeometry] object of indeterminate type into a
+/// [FractionalOffset] object, call the [resolve] method.
+abstract class FractionalOffsetGeometry {
+ /// Abstract const constructor. This constructor enables subclasses to provide
+ /// const constructors so that they can be used in const expressions.
+ const FractionalOffsetGeometry();
+
+ double get _dx;
+ double get _start;
+ double get _dy;
+
+ /// Returns the difference between two [FractionalOffsetGeometry] objects.
+ ///
+ /// If you know you are applying this to two [FractionalOffset]s or two
+ /// [FractionalOffsetDirectional] objects, consider using the binary infix `-`
+ /// operator instead, which always returns an object of the same type as the
+ /// operands, and is typed accordingly.
+ ///
+ /// If [subtract] is applied to two objects of the same type ([FractionalOffset] or
+ /// [FractionalOffsetDirectional]), an object of that type will be returned (though
+ /// this is not reflected in the type system). Otherwise, an object
+ /// representing a combination of both is returned. That object can be turned
+ /// into a concrete [FractionalOffset] using [resolve].
+ ///
+ /// This method returns the same result as [add] applied to the result of
+ /// negating the argument (using the prefix unary `-` operator or multiplying
+ /// the argument by -1.0 using the `*` operator).
+ FractionalOffsetGeometry subtract(FractionalOffsetGeometry other) {
+ return new _MixedFractionalOffset(
+ _dx - other._dx,
+ _start - other._start,
+ _dy - other._dy,
+ );
+ }
+
+ /// Returns the sum of two [FractionalOffsetGeometry] objects.
+ ///
+ /// If you know you are adding two [FractionalOffset] or two [FractionalOffsetDirectional]
+ /// objects, consider using the `+` operator instead, which always returns an
+ /// object of the same type as the operands, and is typed accordingly.
+ ///
+ /// If [add] is applied to two objects of the same type ([FractionalOffset] or
+ /// [FractionalOffsetDirectional]), an object of that type will be returned (though
+ /// this is not reflected in the type system). Otherwise, an object
+ /// representing a combination of both is returned. That object can be turned
+ /// into a concrete [FractionalOffset] using [resolve].
+ FractionalOffsetGeometry add(FractionalOffsetGeometry other) {
+ return new _MixedFractionalOffset(
+ _dx + other._dx,
+ _start + other._start,
+ _dy + other._dy,
+ );
+ }
+
+ /// Returns the negation of the given [FractionalOffsetGeometry] object.
+ ///
+ /// This is the same as multiplying the object by -1.0.
+ ///
+ /// This operator returns an object of the same type as the operand.
+ FractionalOffsetGeometry operator -();
+
+ /// Scales the [FractionalOffsetGeometry] object in each dimension by the given factor.
+ ///
+ /// This operator returns an object of the same type as the operand.
+ FractionalOffsetGeometry operator *(double other);
+
+ /// Divides the [FractionalOffsetGeometry] object in each dimension by the given factor.
+ ///
+ /// This operator returns an object of the same type as the operand.
+ FractionalOffsetGeometry operator /(double other);
+
+ /// Integer divides the [FractionalOffsetGeometry] object in each dimension by the given factor.
+ ///
+ /// This operator returns an object of the same type as the operand.
+ FractionalOffsetGeometry operator ~/(double other);
+
+ /// Computes the remainder in each dimension by the given factor.
+ ///
+ /// This operator returns an object of the same type as the operand.
+ FractionalOffsetGeometry operator %(double other);
+
+ /// Linearly interpolate between two [FractionalOffsetGeometry] objects.
+ ///
+ /// If either is null, this function interpolates from [FractionalOffset.center], and
+ /// the result is an object of the same type as the non-null argument.
+ ///
+ /// If [lerp] is applied to two objects of the same type ([FractionalOffset] or
+ /// [FractionalOffsetDirectional]), an object of that type will be returned (though
+ /// this is not reflected in the type system). Otherwise, an object
+ /// representing a combination of both is returned. That object can be turned
+ /// into a concrete [FractionalOffset] using [resolve].
+ static FractionalOffsetGeometry lerp(FractionalOffsetGeometry a, FractionalOffsetGeometry b, double t) {
+ if (a == null && b == null)
+ return null;
+ if ((a == null || a is FractionalOffset) && (b == null || b is FractionalOffset))
+ return FractionalOffset.lerp(a, b, t);
+ if ((a == null || a is FractionalOffsetDirectional) && (b == null || b is FractionalOffsetDirectional))
+ return FractionalOffsetDirectional.lerp(a, b, t);
+ if (a == null) {
+ return new _MixedFractionalOffset(
+ ui.lerpDouble(0.5, b._dx, t),
+ ui.lerpDouble(0.0, b._start, t),
+ ui.lerpDouble(0.5, b._dy, t),
+ );
+ }
+ if (b == null) {
+ return new _MixedFractionalOffset(
+ ui.lerpDouble(a._dx, 0.5, t),
+ ui.lerpDouble(a._start, 0.0, t),
+ ui.lerpDouble(a._dy, 0.5, t),
+ );
+ }
+ return new _MixedFractionalOffset(
+ ui.lerpDouble(a._dx, b._dx, t),
+ ui.lerpDouble(a._start, b._start, t),
+ ui.lerpDouble(a._dy, b._dy, t),
+ );
+ }
+
+ /// Convert this instance into a [FractionalOffset], which uses literal
+ /// coordinates (the `x` coordinate being explicitly a distance from the
+ /// left).
+ ///
+ /// See also:
+ ///
+ /// * [FractionalOffset], for which this is a no-op (returns itself).
+ /// * [FractionalOffsetDirectional], which flips the horizontal direction
+ /// based on the `direction` argument.
+ FractionalOffset resolve(TextDirection direction);
+
+ @override
+ String toString() {
+ double x = _dx;
+ double start = _start;
+ if (this is FractionalOffset) {
+ assert(start == 0.0);
+ start = null;
+ } else if (start == 0.5) {
+ x += 0.5;
+ start = null;
+ }
+ if (start == null) {
+ if (x == 0.0 && _dy == 0.0)
+ return 'FractionalOffset.topLeft';
+ if (x == 0.5 && _dy == 0.0)
+ return 'FractionalOffset.topCenter';
+ if (x == 1.0 && _dy == 0.0)
+ return 'FractionalOffset.topRight';
+ if (x == 0.0 && _dy == 0.5)
+ return 'FractionalOffset.centerLeft';
+ if (x == 0.5 && _dy == 0.5)
+ return 'FractionalOffset.center';
+ if (x == 1.0 && _dy == 0.5)
+ return 'FractionalOffset.centerRight';
+ if (x == 0.0 && _dy == 1.0)
+ return 'FractionalOffset.bottomLeft';
+ if (x == 0.5 && _dy == 1.0)
+ return 'FractionalOffset.bottomCenter';
+ if (x == 1.0 && _dy == 1.0)
+ return 'FractionalOffset.bottomRight';
+ return 'FractionalOffset(${x.toStringAsFixed(1)}, '
+ '${_dy.toStringAsFixed(1)})';
+ } else if (x == 0.0) {
+ assert(start != 0.5);
+ if (start == 0.0 && _dy == 0.0)
+ return 'FractionalOffsetDirectional.topStart';
+ if (start == 1.0 && _dy == 0.0)
+ return 'FractionalOffsetDirectional.topEnd';
+ if (start == 0.0 && _dy == 0.5)
+ return 'FractionalOffsetDirectional.centerStart';
+ if (start == 1.0 && _dy == 0.5)
+ return 'FractionalOffsetDirectional.centerEnd';
+ if (start == 0.0 && _dy == 1.0)
+ return 'FractionalOffsetDirectional.bottomStart';
+ if (start == 1.0 && _dy == 1.0)
+ return 'FractionalOffsetDirectional.bottomEnd';
+ return 'FractionalOffsetDirectional(${start.toStringAsFixed(1)}, '
+ '${_dy.toStringAsFixed(1)})';
+ }
+ return 'FractionalOffset(${_dx.toStringAsFixed(1)}, '
+ '${_dy.toStringAsFixed(1)})'
+ ' + '
+ 'FractionalOffsetDirectional(${_start.toStringAsFixed(1)}, '
+ '0.0)';
+ }
+
+ @override
+ bool operator ==(dynamic other) {
+ if (other is! FractionalOffsetGeometry)
+ return false;
+ final FractionalOffsetGeometry typedOther = other;
+ return _dx == typedOther._dx &&
+ _start == typedOther._start &&
+ _dy == typedOther._dy;
+ }
+
+ @override
+ int get hashCode => hashValues(_dx, _start, _dy);
+}
+
/// An offset that's expressed as a fraction of a [Size].
///
/// `FractionalOffset(1.0, 0.0)` represents the top right of the [Size].
@@ -22,8 +228,14 @@
///
/// * [Align] positions a child according to a [FractionalOffset].
/// * [FractionalTranslation] moves a child according to a [FractionalOffset].
+///
+/// See also:
+///
+/// * [FractionalOffsetDirectional], which (for properties and arguments that
+/// accept the type [FractionalOffsetGeometry]) allows the horizontal
+/// coordinate to be specified in a [TextDirection]-aware manner.
@immutable
-class FractionalOffset {
+class FractionalOffset extends FractionalOffsetGeometry {
/// Creates a fractional offset.
///
/// The [dx] and [dy] arguments must not be null.
@@ -66,14 +278,23 @@
/// edge.
final double dx;
+ @override
+ double get _dx => dx;
+
/// The distance fraction in the vertical direction.
///
/// A value of 0.0 corresponds to the topmost edge. A value of 1.0 corresponds
/// to the bottommost edge. Values are not limited to that range; negative
- /// values represent positions above the top, and values greated than 1.0
+ /// values represent positions above the top, and values greater than 1.0
/// represent positions below the bottom.
final double dy;
+ @override
+ double get _dy => dy;
+
+ @override
+ double get _start => 0.0;
+
/// The top left corner.
static const FractionalOffset topLeft = const FractionalOffset(0.0, 0.0);
@@ -83,6 +304,15 @@
/// The top right corner.
static const FractionalOffset topRight = const FractionalOffset(1.0, 0.0);
+ /// The center point along the left edge.
+ static const FractionalOffset centerLeft = const FractionalOffset(0.0, 0.5);
+
+ /// The center point, both horizontally and vertically.
+ static const FractionalOffset center = const FractionalOffset(0.5, 0.5);
+
+ /// The center point along the right edge.
+ static const FractionalOffset centerRight = const FractionalOffset(1.0, 0.5);
+
/// The bottom left corner.
static const FractionalOffset bottomLeft = const FractionalOffset(0.0, 1.0);
@@ -92,46 +322,56 @@
/// The bottom right corner.
static const FractionalOffset bottomRight = const FractionalOffset(1.0, 1.0);
- /// The center point along the left edge.
- static const FractionalOffset centerLeft = const FractionalOffset(0.0, 0.5);
-
- /// The center point along the right edge.
- static const FractionalOffset centerRight = const FractionalOffset(1.0, 0.5);
-
- /// The center point, both horizontally and vertically.
- static const FractionalOffset center = const FractionalOffset(0.5, 0.5);
-
- /// Returns the negation of the given fractional offset.
- FractionalOffset operator -() {
- return new FractionalOffset(-dx, -dy);
+ @override
+ FractionalOffsetGeometry subtract(FractionalOffsetGeometry other) {
+ if (other is FractionalOffset)
+ return this - other;
+ return super.subtract(other);
}
- /// Returns the difference between two fractional offsets.
+ @override
+ FractionalOffsetGeometry add(FractionalOffsetGeometry other) {
+ if (other is FractionalOffset)
+ return this + other;
+ return super.add(other);
+ }
+
+ /// Returns the difference between two [FractionalOffset]s.
FractionalOffset operator -(FractionalOffset other) {
return new FractionalOffset(dx - other.dx, dy - other.dy);
}
- /// Returns the sum of two fractional offsets.
+ /// Returns the sum of two [FractionalOffset]s.
FractionalOffset operator +(FractionalOffset other) {
return new FractionalOffset(dx + other.dx, dy + other.dy);
}
- /// Scales the fractional offset in each dimension by the given factor.
+ /// Returns the negation of the given [FractionalOffset].
+ @override
+ FractionalOffset operator -() {
+ return new FractionalOffset(-dx, -dy);
+ }
+
+ /// Scales the [FractionalOffset] in each dimension by the given factor.
+ @override
FractionalOffset operator *(double other) {
return new FractionalOffset(dx * other, dy * other);
}
- /// Divides the fractional offset in each dimension by the given factor.
+ /// Divides the [FractionalOffset] in each dimension by the given factor.
+ @override
FractionalOffset operator /(double other) {
return new FractionalOffset(dx / other, dy / other);
}
- /// Integer divides the fractional offset in each dimension by the given factor.
+ /// Integer divides the [FractionalOffset] in each dimension by the given factor.
+ @override
FractionalOffset operator ~/(double other) {
return new FractionalOffset((dx ~/ other).toDouble(), (dy ~/ other).toDouble());
}
/// Computes the remainder in each dimension by the given factor.
+ @override
FractionalOffset operator %(double other) {
return new FractionalOffset(dx % other, dy % other);
}
@@ -161,37 +401,261 @@
rect.left + (rect.width - size.width) * dx,
rect.top + (rect.height - size.height) * dy,
size.width,
- size.height
+ size.height,
);
}
- @override
- bool operator ==(dynamic other) {
- if (other is! FractionalOffset)
- return false;
- final FractionalOffset typedOther = other;
- return dx == typedOther.dx &&
- dy == typedOther.dy;
- }
-
- @override
- int get hashCode => hashValues(dx, dy);
-
- /// Linearly interpolate between two EdgeInsets.
+ /// Linearly interpolate between two [FractionalOffset]s.
///
- /// If either is null, this function interpolates from [FractionalOffset.topLeft].
- // TODO(abarth): Consider interpolating from [FractionalOffset.center] instead
- // to remove upper-left bias.
+ /// If either is null, this function interpolates from [FractionalOffset.center].
static FractionalOffset lerp(FractionalOffset a, FractionalOffset b, double t) {
if (a == null && b == null)
return null;
if (a == null)
- return new FractionalOffset(b.dx * t, b.dy * t);
+ return new FractionalOffset(ui.lerpDouble(0.5, b.dx, t), ui.lerpDouble(0.5, b.dy, t));
if (b == null)
- return new FractionalOffset(a.dx * (1.0 - t), a.dy * (1.0 - t));
+ return new FractionalOffset(ui.lerpDouble(a.dx, 0.5, t), ui.lerpDouble(a.dy, 0.5, t));
return new FractionalOffset(ui.lerpDouble(a.dx, b.dx, t), ui.lerpDouble(a.dy, b.dy, t));
}
@override
- String toString() => '$runtimeType($dx, $dy)';
+ FractionalOffset resolve(TextDirection direction) => this;
+}
+
+/// An offset that's expressed as a fraction of a [Size], but whose horizontal
+/// component is dependent on the writing direction.
+///
+/// This can be used to indicate an offset from the left in [TextDirection.ltr]
+/// text and an offset from the right in [TextDirection.rtl] text without having
+/// to be aware of the current text direction.
+///
+/// See also:
+///
+/// * [FractionalOffset], a variant that is defined in physical terms (i.e.
+/// whose horizontal component does not depend on the text direction).
+class FractionalOffsetDirectional extends FractionalOffsetGeometry {
+ /// Creates a directional fractional offset.
+ ///
+ /// The [start] and [dy] arguments must not be null.
+ const FractionalOffsetDirectional(this.start, this.dy)
+ : assert(start != null),
+ assert(dy != null);
+
+ /// The distance fraction in the horizontal direction.
+ ///
+ /// A value of 0.0 corresponds to the edge on the "start" side, which is the
+ /// left side in [TextDirection.ltr] contexts and the right side in
+ /// [TextDirection.rtl] contexts. A value of 1.0 corresponds to the opposite
+ /// edge, the "end" side. Values are not limited to that range; negative
+ /// values represent positions beyond the start edge, and values greater than
+ /// 1.0 represent positions beyond the end edge.
+ ///
+ /// This value is normalized into a [FractionalOffset.dx] value by the
+ /// [resolve] method.
+ final double start;
+
+ @override
+ double get _start => start;
+
+ /// The distance fraction in the vertical direction.
+ ///
+ /// A value of 0.0 corresponds to the topmost edge. A value of 1.0 corresponds
+ /// to the bottommost edge. Values are not limited to that range; negative
+ /// values represent positions above the top, and values greater than 1.0
+ /// represent positions below the bottom.
+ ///
+ /// This value is passed through to [FractionalOffset.dy] unmodified by the
+ /// [resolve] method.
+ final double dy;
+
+ @override
+ double get _dy => dy;
+
+ @override
+ double get _dx => 0.0;
+
+ /// The top corner on the "start" side.
+ static const FractionalOffsetDirectional topStart = const FractionalOffsetDirectional(0.0, 0.0);
+
+ /// The center point along the top edge.
+ ///
+ /// Consider using [FractionalOffset.topCenter] instead, as it does not need
+ /// to be [resolve]d to be used.
+ static const FractionalOffsetDirectional topCenter = const FractionalOffsetDirectional(0.5, 0.0);
+
+ /// The top corner on the "end" side.
+ static const FractionalOffsetDirectional topEnd = const FractionalOffsetDirectional(1.0, 0.0);
+
+ /// The center point along the "start" edge.
+ static const FractionalOffsetDirectional centerStart = const FractionalOffsetDirectional(0.0, 0.5);
+
+ /// The center point, both horizontally and vertically.
+ ///
+ /// Consider using [FractionalOffset.center] instead, as it does not need to
+ /// be [resolve]d to be used.
+ static const FractionalOffsetDirectional center = const FractionalOffsetDirectional(0.5, 0.5);
+
+ /// The center point along the "end" edge.
+ static const FractionalOffsetDirectional centerEnd = const FractionalOffsetDirectional(1.0, 0.5);
+
+ /// The bottom corner on the "start" side.
+ static const FractionalOffsetDirectional bottomStart = const FractionalOffsetDirectional(0.0, 1.0);
+
+ /// The center point along the bottom edge.
+ ///
+ /// Consider using [FractionalOffset.bottomCenter] instead, as it does not
+ /// need to be [resolve]d to be used.
+ static const FractionalOffsetDirectional bottomCenter = const FractionalOffsetDirectional(0.5, 1.0);
+
+ /// The bottom corner on the "end" side.
+ static const FractionalOffsetDirectional bottomEnd = const FractionalOffsetDirectional(1.0, 1.0);
+
+ @override
+ FractionalOffsetGeometry subtract(FractionalOffsetGeometry other) {
+ if (other is FractionalOffsetDirectional)
+ return this - other;
+ return super.subtract(other);
+ }
+
+ @override
+ FractionalOffsetGeometry add(FractionalOffsetGeometry other) {
+ if (other is FractionalOffsetDirectional)
+ return this + other;
+ return super.add(other);
+ }
+
+ /// Returns the difference between two [FractionalOffsetDirectional]s.
+ FractionalOffsetDirectional operator -(FractionalOffsetDirectional other) {
+ return new FractionalOffsetDirectional(start - other.start, dy - other.dy);
+ }
+
+ /// Returns the sum of two [FractionalOffsetDirectional]s.
+ FractionalOffsetDirectional operator +(FractionalOffsetDirectional other) {
+ return new FractionalOffsetDirectional(start + other.start, dy + other.dy);
+ }
+
+ /// Returns the negation of the given [FractionalOffsetDirectional].
+ @override
+ FractionalOffsetDirectional operator -() {
+ return new FractionalOffsetDirectional(-start, -dy);
+ }
+
+ /// Scales the [FractionalOffsetDirectional] in each dimension by the given factor.
+ @override
+ FractionalOffsetDirectional operator *(double other) {
+ return new FractionalOffsetDirectional(start * other, dy * other);
+ }
+
+ /// Divides the [FractionalOffsetDirectional] in each dimension by the given factor.
+ @override
+ FractionalOffsetDirectional operator /(double other) {
+ return new FractionalOffsetDirectional(start / other, dy / other);
+ }
+
+ /// Integer divides the [FractionalOffsetDirectional] in each dimension by the given factor.
+ @override
+ FractionalOffsetDirectional operator ~/(double other) {
+ return new FractionalOffsetDirectional((start ~/ other).toDouble(), (dy ~/ other).toDouble());
+ }
+
+ /// Computes the remainder in each dimension by the given factor.
+ @override
+ FractionalOffsetDirectional operator %(double other) {
+ return new FractionalOffsetDirectional(start % other, dy % other);
+ }
+
+ /// Linearly interpolate between two [FractionalOffsetDirectional]s.
+ ///
+ /// If either is null, this function interpolates from [FractionalOffset.center].
+ static FractionalOffsetDirectional lerp(FractionalOffsetDirectional a, FractionalOffsetDirectional b, double t) {
+ if (a == null && b == null)
+ return null;
+ if (a == null)
+ return new FractionalOffsetDirectional(ui.lerpDouble(0.5, b.start, t), ui.lerpDouble(0.5, b.dy, t));
+ if (b == null)
+ return new FractionalOffsetDirectional(ui.lerpDouble(a.start, 0.5, t), ui.lerpDouble(a.dy, 0.5, t));
+ return new FractionalOffsetDirectional(ui.lerpDouble(a.start, b.start, t), ui.lerpDouble(a.dy, b.dy, t));
+ }
+
+ @override
+ FractionalOffset resolve(TextDirection direction) {
+ assert(direction != null);
+ switch (direction) {
+ case TextDirection.ltr:
+ return new FractionalOffset(start, dy);
+ case TextDirection.rtl:
+ return new FractionalOffset(1.0 - start, dy);
+ }
+ return null;
+ }
+}
+
+class _MixedFractionalOffset extends FractionalOffsetGeometry {
+ const _MixedFractionalOffset(this._dx, this._start, this._dy);
+
+ @override
+ final double _dx;
+
+ @override
+ final double _start;
+
+ @override
+ final double _dy;
+
+ @override
+ _MixedFractionalOffset operator -() {
+ return new _MixedFractionalOffset(
+ -_dx,
+ -_start,
+ -_dy,
+ );
+ }
+
+ @override
+ _MixedFractionalOffset operator *(double other) {
+ return new _MixedFractionalOffset(
+ _dx * other,
+ _start * other,
+ _dy * other,
+ );
+ }
+
+ @override
+ _MixedFractionalOffset operator /(double other) {
+ return new _MixedFractionalOffset(
+ _dx / other,
+ _start / other,
+ _dy / other,
+ );
+ }
+
+ @override
+ _MixedFractionalOffset operator ~/(double other) {
+ return new _MixedFractionalOffset(
+ (_dx ~/ other).toDouble(),
+ (_start ~/ other).toDouble(),
+ (_dy ~/ other).toDouble(),
+ );
+ }
+
+ @override
+ _MixedFractionalOffset operator %(double other) {
+ return new _MixedFractionalOffset(
+ _dx % other,
+ _start % other,
+ _dy % other,
+ );
+ }
+
+ @override
+ FractionalOffset resolve(TextDirection direction) {
+ assert(direction != null);
+ switch (direction) {
+ case TextDirection.ltr:
+ return new FractionalOffset(_start + _dx, _dy);
+ case TextDirection.rtl:
+ return new FractionalOffset((1.0 - _start) + _dx, _dy);
+ }
+ return null;
+ }
}
diff --git a/packages/flutter/lib/src/rendering/box.dart b/packages/flutter/lib/src/rendering/box.dart
index 3cbad71..30f8f40 100644
--- a/packages/flutter/lib/src/rendering/box.dart
+++ b/packages/flutter/lib/src/rendering/box.dart
@@ -2149,6 +2149,11 @@
///
/// Stops walking once after the first child reports that it contains the
/// 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 }) {
// the x, y parameters have the top left of the node's box as the origin
ChildType child = lastChild;
@@ -2162,6 +2167,11 @@
}
/// 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) {
ChildType child = firstChild;
while (child != null) {
diff --git a/packages/flutter/lib/src/rendering/flex.dart b/packages/flutter/lib/src/rendering/flex.dart
index 0c31dce..9d89645 100644
--- a/packages/flutter/lib/src/rendering/flex.dart
+++ b/packages/flutter/lib/src/rendering/flex.dart
@@ -100,9 +100,21 @@
/// * [RenderFlex], the flex render object.
enum MainAxisAlignment {
/// Place the children as close to the start of the main axis as possible.
+ ///
+ /// If this value is used in a horizontal direction, a [TextDirection] must be
+ /// available to determine if the start is the left or the right.
+ ///
+ /// If this value is used in a vertical direction, a [VerticalDirection] must be
+ /// available to determine if the start is the top or the bottom.
start,
/// Place the children as close to the end of the main axis as possible.
+ ///
+ /// If this value is used in a horizontal direction, a [TextDirection] must be
+ /// available to determine if the end is the left or the right.
+ ///
+ /// If this value is used in a vertical direction, a [VerticalDirection] must be
+ /// available to determine if the end is the top or the bottom.
end,
/// Place the children as close to the middle of the main axis as possible.
@@ -130,14 +142,28 @@
/// Place the children with their start edge aligned with the start side of
/// the cross axis.
///
- /// For example, in a column (a flex with a vertical axis), this aligns the
- /// left edge of the children along the left edge of the column.
+ /// For example, in a column (a flex with a vertical axis) whose
+ /// [TextDirection] is [TextDirection.ltr], this aligns the left edge of the
+ /// children along the left edge of the column.
+ ///
+ /// If this value is used in a horizontal direction, a [TextDirection] must be
+ /// available to determine if the start is the left or the right.
+ ///
+ /// If this value is used in a vertical direction, a [VerticalDirection] must be
+ /// available to determine if the start is the top or the bottom.
start,
/// Place the children as close to the end of the cross axis as possible.
///
- /// For example, in a column (a flex with a vertical axis), this aligns the
- /// right edge of the children along the right edge of the column.
+ /// For example, in a column (a flex with a vertical axis) whose
+ /// [TextDirection] is [TextDirection.ltr], this aligns the right edge of the
+ /// children along the right edge of the column.
+ ///
+ /// If this value is used in a horizontal direction, a [TextDirection] must be
+ /// available to determine if the end is the left or the right.
+ ///
+ /// If this value is used in a vertical direction, a [VerticalDirection] must be
+ /// available to determine if the end is the top or the bottom.
end,
/// Place the children so that their centers align with the middle of the
@@ -159,10 +185,40 @@
baseline,
}
+bool _startIsTopLeft(Axis direction, TextDirection textDirection, VerticalDirection verticalDirection) {
+ assert(direction != null);
+ // If the relevant value of textDirection or verticalDirection is null, this returns null too.
+ switch (direction) {
+ case Axis.horizontal:
+ switch (textDirection) {
+ case TextDirection.ltr:
+ return true;
+ case TextDirection.rtl:
+ return false;
+ }
+ break;
+ case Axis.vertical:
+ switch (verticalDirection) {
+ case VerticalDirection.down:
+ return true;
+ case VerticalDirection.up:
+ return false;
+ }
+ break;
+ }
+ return null;
+}
+
typedef double _ChildSizingFunction(RenderBox child, double extent);
/// Displays its children in a one-dimensional array.
///
+/// ## Layout algorithm
+///
+/// _This section describes how the framework causes [RenderFlex] to position
+/// its children._
+/// _See [BoxConstraints] for an introduction to box layout models._
+///
/// Layout for a [RenderFlex] proceeds in six steps:
///
/// 1. Layout each child a null or zero flex factor with unbounded main axis
@@ -197,6 +253,11 @@
/// [mainAxisAlignment] is [MainAxisAlignment.spaceBetween], any main axis
/// space that has not been allocated to children is divided evenly and
/// placed between the children.
+///
+/// See also:
+///
+/// * [Flex], the widget equivalent.
+/// * [Row] and [Column], direction-specific variants of [Flex].
class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, FlexParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, FlexParentData> {
/// Creates a flex render object.
@@ -209,7 +270,9 @@
MainAxisSize mainAxisSize: MainAxisSize.max,
MainAxisAlignment mainAxisAlignment: MainAxisAlignment.start,
CrossAxisAlignment crossAxisAlignment: CrossAxisAlignment.center,
- TextBaseline textBaseline
+ TextDirection textDirection,
+ VerticalDirection verticalDirection: VerticalDirection.down,
+ TextBaseline textBaseline,
}) : assert(direction != null),
assert(mainAxisAlignment != null),
assert(mainAxisSize != null),
@@ -218,6 +281,8 @@
_mainAxisAlignment = mainAxisAlignment,
_mainAxisSize = mainAxisSize,
_crossAxisAlignment = crossAxisAlignment,
+ _textDirection = textDirection,
+ _verticalDirection = verticalDirection,
_textBaseline = textBaseline {
addAll(children);
}
@@ -234,6 +299,14 @@
}
/// How the children should be placed along the main axis.
+ ///
+ /// If the [direction] is [Axis.horizontal], and the [mainAxisAlignment] is
+ /// either [MainAxisAlignment.start] or [MainAxisAlignment.end], then the
+ /// [textDirection] must not be null.
+ ///
+ /// If the [direction] is [Axis.vertical], and the [mainAxisAlignment] is
+ /// either [MainAxisAlignment.start] or [MainAxisAlignment.end], then the
+ /// [verticalDirection] must not be null.
MainAxisAlignment get mainAxisAlignment => _mainAxisAlignment;
MainAxisAlignment _mainAxisAlignment;
set mainAxisAlignment(MainAxisAlignment value) {
@@ -265,6 +338,14 @@
}
/// How the children should be placed along the cross axis.
+ ///
+ /// If the [direction] is [Axis.horizontal], and the [crossAxisAlignment] is
+ /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the
+ /// [verticalDirection] must not be null.
+ ///
+ /// If the [direction] is [Axis.vertical], and the [crossAxisAlignment] is
+ /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the
+ /// [textDirection] must not be null.
CrossAxisAlignment get crossAxisAlignment => _crossAxisAlignment;
CrossAxisAlignment _crossAxisAlignment;
set crossAxisAlignment(CrossAxisAlignment value) {
@@ -275,6 +356,62 @@
}
}
+ /// Determines the order to lay children out horizontally and how to interpret
+ /// `start` and `end` in the horizontal direction.
+ ///
+ /// If the [direction] is [Axis.horizontal], this controls which order
+ /// children are painted in (left-to-right or right-to-left), and the meaning
+ /// of the [mainAxisAlignment] property's [MainAxisAlignment.start] and
+ /// [MainAxisAlignment.end] values.
+ ///
+ /// If the [direction] is [Axis.horizontal], and either the
+ /// [mainAxisAlignment] is either [MainAxisAlignment.start] or
+ /// [MainAxisAlignment.end], or there's more than one child, then the
+ /// [textDirection] must not be null.
+ ///
+ /// If the [direction] is [Axis.vertical], this controls the meaning of the
+ /// [crossAxisAlignment] property's [CrossAxisAlignment.start] and
+ /// [CrossAxisAlignment.end] values.
+ ///
+ /// If the [direction] is [Axis.vertical], and the [crossAxisAlignment] is
+ /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the
+ /// [textDirection] must not be null.
+ TextDirection get textDirection => _textDirection;
+ TextDirection _textDirection;
+ set textDirection(TextDirection value) {
+ if (_textDirection != value) {
+ _textDirection = value;
+ markNeedsLayout();
+ }
+ }
+
+ /// Determines the order to lay children out vertically and how to interpret
+ /// `start` and `end` in the vertical direction.
+ ///
+ /// If the [direction] is [Axis.vertical], this controls which order children
+ /// are painted in (down or up), the meaning of the [mainAxisAlignment]
+ /// property's [MainAxisAlignment.start] and [MainAxisAlignment.end] values.
+ ///
+ /// If the [direction] is [Axis.vertical], and either the [mainAxisAlignment]
+ /// is either [MainAxisAlignment.start] or [MainAxisAlignment.end], or there's
+ /// more than one child, then the [verticalDirection] must not be null.
+ ///
+ /// If the [direction] is [Axis.horizontal], this controls the meaning of the
+ /// [crossAxisAlignment] property's [CrossAxisAlignment.start] and
+ /// [CrossAxisAlignment.end] values.
+ ///
+ /// If the [direction] is [Axis.horizontal], and the [crossAxisAlignment] is
+ /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the
+ /// [verticalDirection] must not be null.
+ VerticalDirection get verticalDirection => _verticalDirection;
+ VerticalDirection _verticalDirection;
+ set verticalDirection(VerticalDirection value) {
+ if (_verticalDirection != value) {
+ _verticalDirection = value;
+ markNeedsLayout();
+ }
+ }
+
/// If aligning items according to their baseline, which baseline to use.
///
/// Must not be null if [crossAxisAlignment] is [CrossAxisAlignment.baseline].
@@ -288,6 +425,45 @@
}
}
+ bool get _debugHasNecessaryDirections {
+ assert(direction != null);
+ assert(crossAxisAlignment != null);
+ if (firstChild != null && lastChild != firstChild) {
+ // i.e. there's more than one child
+ switch (direction) {
+ case Axis.horizontal:
+ assert(textDirection != null, 'Horizontal $runtimeType with multiple children has a null textDirection, so the layout order is undefined.');
+ break;
+ case Axis.vertical:
+ assert(verticalDirection != null, 'Vertical $runtimeType with multiple children has a null verticalDirection, so the layout order is undefined.');
+ break;
+ }
+ }
+ if (mainAxisAlignment == MainAxisAlignment.start ||
+ mainAxisAlignment == MainAxisAlignment.end) {
+ switch (direction) {
+ case Axis.horizontal:
+ assert(textDirection != null, 'Horizontal $runtimeType with $mainAxisAlignment has a null textDirection, so the alignment cannot be resolved.');
+ break;
+ case Axis.vertical:
+ assert(verticalDirection != null, 'Vertical $runtimeType with $mainAxisAlignment has a null verticalDirection, so the alignment cannot be resolved.');
+ break;
+ }
+ }
+ if (crossAxisAlignment == CrossAxisAlignment.start ||
+ crossAxisAlignment == CrossAxisAlignment.end) {
+ switch (direction) {
+ case Axis.horizontal:
+ assert(verticalDirection != null, 'Horizontal $runtimeType with $crossAxisAlignment has a null verticalDirection, so the alignment cannot be resolved.');
+ break;
+ case Axis.vertical:
+ assert(textDirection != null, 'Vertical $runtimeType with $crossAxisAlignment has a null textDirection, so the alignment cannot be resolved.');
+ break;
+ }
+ }
+ return true;
+ }
+
/// Set during layout if overflow occurred on the main axis.
double _overflow;
@@ -453,6 +629,7 @@
@override
void performLayout() {
+ assert(_debugHasNecessaryDirections);
// Determine used flex factor, size inflexible items, calculate free space.
int totalFlex = 0;
int totalChildren = 0;
@@ -632,9 +809,10 @@
// Align items along the main axis.
double actualSizeDelta;
+ double preferredSize;
if (canFlex) {
final bool isMainAxisSizeMax = mainAxisSize == MainAxisSize.max;
- final double preferredSize = isMainAxisSizeMax ? maxMainSize : allocatedSize;
+ preferredSize = isMainAxisSizeMax ? maxMainSize : allocatedSize;
switch (_direction) {
case Axis.horizontal:
size = constraints.constrain(new Size(preferredSize, crossSize));
@@ -653,21 +831,27 @@
switch (_direction) {
case Axis.horizontal:
size = constraints.constrain(new Size(allocatedSize, crossSize));
+ preferredSize = size.width;
crossSize = size.height;
- actualSizeDelta = size.width - allocatedSize;
break;
case Axis.vertical:
size = constraints.constrain(new Size(crossSize, allocatedSize));
+ preferredSize = size.height;
crossSize = size.width;
- actualSizeDelta = size.height - allocatedSize;
break;
}
+ actualSizeDelta = preferredSize - allocatedSize;
}
_overflow = math.max(0.0, -actualSizeDelta);
final double remainingSpace = math.max(0.0, actualSizeDelta);
double leadingSpace;
double betweenSpace;
+ // flipMainAxis is used to decide whether to lay out left-to-right/top-to-bottom (false), or
+ // right-to-left/bottom-to-top (true). The _startIsTopLeft will return null if there's only
+ // one child and the relevant direction is null, in which case we arbitrarily decide not to
+ // flip, but that doesn't have any detectable effect.
+ final bool flipMainAxis = !(_startIsTopLeft(direction, textDirection, verticalDirection) ?? true);
switch (_mainAxisAlignment) {
case MainAxisAlignment.start:
leadingSpace = 0.0;
@@ -696,22 +880,25 @@
}
// Position elements
- double childMainPosition = leadingSpace;
+ double childMainPosition = flipMainAxis ? preferredSize - leadingSpace : leadingSpace;
child = firstChild;
while (child != null) {
final FlexParentData childParentData = child.parentData;
double childCrossPosition;
switch (_crossAxisAlignment) {
- case CrossAxisAlignment.stretch:
case CrossAxisAlignment.start:
- childCrossPosition = 0.0;
- break;
case CrossAxisAlignment.end:
- childCrossPosition = crossSize - _getCrossSize(child);
+ childCrossPosition = _startIsTopLeft(flipAxis(direction), textDirection, verticalDirection)
+ == (_crossAxisAlignment == CrossAxisAlignment.start)
+ ? 0.0
+ : crossSize - _getCrossSize(child);
break;
case CrossAxisAlignment.center:
childCrossPosition = crossSize / 2.0 - _getCrossSize(child) / 2.0;
break;
+ case CrossAxisAlignment.stretch:
+ childCrossPosition = 0.0;
+ break;
case CrossAxisAlignment.baseline:
childCrossPosition = 0.0;
if (_direction == Axis.horizontal) {
@@ -722,6 +909,8 @@
}
break;
}
+ if (flipMainAxis)
+ childMainPosition -= _getMainSize(child);
switch (_direction) {
case Axis.horizontal:
childParentData.offset = new Offset(childMainPosition, childCrossPosition);
@@ -730,7 +919,11 @@
childParentData.offset = new Offset(childCrossPosition, childMainPosition);
break;
}
- childMainPosition += _getMainSize(child) + betweenSpace;
+ if (flipMainAxis) {
+ childMainPosition -= betweenSpace;
+ } else {
+ childMainPosition += _getMainSize(child) + betweenSpace;
+ }
child = childParentData.nextSibling;
}
}
@@ -797,11 +990,13 @@
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
- description.add(new EnumProperty<Axis>('direction', _direction));
- description.add(new EnumProperty<MainAxisAlignment>('mainAxisAlignment', _mainAxisAlignment));
- description.add(new EnumProperty<MainAxisSize>('mainAxisSize', _mainAxisSize));
- description.add(new EnumProperty<CrossAxisAlignment>('crossAxisAlignment', _crossAxisAlignment));
- description.add(new EnumProperty<TextBaseline>('textBaseline', _textBaseline));
+ description.add(new EnumProperty<Axis>('direction', direction));
+ description.add(new EnumProperty<MainAxisAlignment>('mainAxisAlignment', mainAxisAlignment));
+ description.add(new EnumProperty<MainAxisSize>('mainAxisSize', mainAxisSize));
+ description.add(new EnumProperty<CrossAxisAlignment>('crossAxisAlignment', crossAxisAlignment));
+ description.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+ description.add(new EnumProperty<VerticalDirection>('verticalDirection', verticalDirection, defaultValue: null));
+ description.add(new EnumProperty<TextBaseline>('textBaseline', textBaseline, defaultValue: null));
}
}
diff --git a/packages/flutter/lib/src/rendering/node.dart b/packages/flutter/lib/src/rendering/node.dart
index a03c964..f7d7d41 100644
--- a/packages/flutter/lib/src/rendering/node.dart
+++ b/packages/flutter/lib/src/rendering/node.dart
@@ -43,7 +43,7 @@
int get depth => _depth;
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].
///
/// Only call this method from overrides of [redepthChildren].
diff --git a/packages/flutter/lib/src/rendering/shifted_box.dart b/packages/flutter/lib/src/rendering/shifted_box.dart
index 6a18f1b..8be09e0 100644
--- a/packages/flutter/lib/src/rendering/shifted_box.dart
+++ b/packages/flutter/lib/src/rendering/shifted_box.dart
@@ -91,29 +91,58 @@
///
/// The [padding] argument must not be null and must have non-negative insets.
RenderPadding({
- @required EdgeInsets padding,
- RenderBox child
+ @required EdgeInsetsGeometry padding,
+ TextDirection textDirection,
+ RenderBox child,
}) : assert(padding != null),
assert(padding.isNonNegative),
+ _textDirection = textDirection,
_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.
- EdgeInsets get padding => _padding;
- EdgeInsets _padding;
- set padding(EdgeInsets value) {
+ ///
+ /// If this is set to an [EdgeInsetsDirectional] object, then [textDirection]
+ /// must be non-null.
+ EdgeInsetsGeometry get padding => _padding;
+ EdgeInsetsGeometry _padding;
+ set padding(EdgeInsetsGeometry value) {
assert(value != null);
assert(value.isNonNegative);
if (_padding == value)
return;
_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
double computeMinIntrinsicWidth(double height) {
- final double totalHorizontalPadding = padding.left + padding.right;
- final double totalVerticalPadding = padding.top + padding.bottom;
+ final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
+ final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
if (child != null) // next line relies on double.INFINITY absorption
return child.getMinIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding;
return totalHorizontalPadding;
@@ -121,8 +150,8 @@
@override
double computeMaxIntrinsicWidth(double height) {
- final double totalHorizontalPadding = padding.left + padding.right;
- final double totalVerticalPadding = padding.top + padding.bottom;
+ final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
+ final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
if (child != null) // next line relies on double.INFINITY absorption
return child.getMaxIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding;
return totalHorizontalPadding;
@@ -130,8 +159,8 @@
@override
double computeMinIntrinsicHeight(double width) {
- final double totalHorizontalPadding = padding.left + padding.right;
- final double totalVerticalPadding = padding.top + padding.bottom;
+ final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
+ final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
if (child != null) // next line relies on double.INFINITY absorption
return child.getMinIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding;
return totalVerticalPadding;
@@ -139,8 +168,8 @@
@override
double computeMaxIntrinsicHeight(double width) {
- final double totalHorizontalPadding = padding.left + padding.right;
- final double totalVerticalPadding = padding.top + padding.bottom;
+ final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
+ final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
if (child != null) // next line relies on double.INFINITY absorption
return child.getMaxIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding;
return totalVerticalPadding;
@@ -148,21 +177,21 @@
@override
void performLayout() {
- assert(padding != null);
+ assert(_resolvedPadding != null);
if (child == null) {
size = constraints.constrain(new Size(
- padding.left + padding.right,
- padding.top + padding.bottom
+ _resolvedPadding.left + _resolvedPadding.right,
+ _resolvedPadding.top + _resolvedPadding.bottom
));
return;
}
- final BoxConstraints innerConstraints = constraints.deflate(padding);
+ final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding);
child.layout(innerConstraints, parentUsesSize: true);
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(
- padding.left + child.size.width + padding.right,
- padding.top + child.size.height + padding.bottom
+ _resolvedPadding.left + child.size.width + _resolvedPadding.right,
+ _resolvedPadding.top + child.size.height + _resolvedPadding.bottom
));
}
@@ -171,7 +200,7 @@
super.debugPaintSize(context, offset);
assert(() {
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;
});
}
@@ -179,7 +208,8 @@
@override
void debugFillProperties(DiagnosticPropertiesBuilder 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));
}
}
diff --git a/packages/flutter/lib/src/rendering/sliver.dart b/packages/flutter/lib/src/rendering/sliver.dart
index a1dee0b..4889d8a 100644
--- a/packages/flutter/lib/src/rendering/sliver.dart
+++ b/packages/flutter/lib/src/rendering/sliver.dart
@@ -94,6 +94,10 @@
/// Specifically, returns [AxisDirection.up] for [AxisDirection.down] (and
/// vice versa), as well as [AxisDirection.left] for [AxisDirection.right] (and
/// vice versa).
+///
+/// See also:
+///
+/// * [flipAxis], which does the same thing for [Axis] values.
AxisDirection flipAxisDirection(AxisDirection axisDirection) {
assert(axisDirection != null);
switch (axisDirection) {
diff --git a/packages/flutter/lib/src/widgets/app.dart b/packages/flutter/lib/src/widgets/app.dart
index 2ef0d55..29962da 100644
--- a/packages/flutter/lib/src/widgets/app.dart
+++ b/packages/flutter/lib/src/widgets/app.dart
@@ -23,6 +23,19 @@
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
/// required for an application.
///
@@ -269,6 +282,14 @@
}
}
+ // 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
void didChangeAppLifecycleState(AppLifecycleState state) { }
@@ -281,7 +302,7 @@
data: new MediaQueryData.fromWindow(ui.window),
child: new Localizations(
locale: widget.locale ?? _locale,
- delegates: widget.localizationsDelegates,
+ delegates: _localizationsDelegates.toList(),
child: new Title(
title: widget.title,
color: widget.color,
diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart
index 27c1866..76c8241 100644
--- a/packages/flutter/lib/src/widgets/basic.dart
+++ b/packages/flutter/lib/src/widgets/basic.dart
@@ -1116,20 +1116,27 @@
super(key: key, child: child);
/// The amount of space by which to inset the child.
- final EdgeInsets padding;
+ final EdgeInsetsGeometry padding;
@override
- RenderPadding createRenderObject(BuildContext context) => new RenderPadding(padding: padding);
+ RenderPadding createRenderObject(BuildContext context) {
+ return new RenderPadding(
+ padding: padding,
+ textDirection: Directionality.of(context),
+ );
+ }
@override
void updateRenderObject(BuildContext context, RenderPadding renderObject) {
- renderObject.padding = padding;
+ renderObject
+ ..padding = padding
+ ..textDirection = Directionality.of(context);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
- description.add(new DiagnosticsProperty<EdgeInsets>('padding', padding));
+ description.add(new DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
}
}
@@ -2058,6 +2065,8 @@
/// The amount of space by which to inset the child sliver.
final EdgeInsets padding;
+ // TODO(ianh): RTL
+
@override
RenderSliverPadding createRenderObject(BuildContext context) => new RenderSliverPadding(padding: padding);
@@ -2495,22 +2504,31 @@
///
/// The [direction] is required.
///
- /// The [direction], [mainAxisAlignment], and [crossAxisAlignment] arguments
- /// must not be null. If [crossAxisAlignment] is
+ /// The [direction], [mainAxisAlignment], [crossAxisAlignment], and
+ /// [verticalDirection] arguments must not be null. If [crossAxisAlignment] is
/// [CrossAxisAlignment.baseline], then [textBaseline] must not be null.
+ ///
+ /// The [textDirection] argument defaults to the ambient [Directionality], if
+ /// any. If there is no ambient directionality, and a text direction is going
+ /// to be necessary to decide which direction to lay the children in or to
+ /// disambiguate `start` or `end` values for the main or cross axis
+ /// directions, the [textDirection] must not be null.
Flex({
Key key,
@required this.direction,
this.mainAxisAlignment: MainAxisAlignment.start,
this.mainAxisSize: MainAxisSize.max,
this.crossAxisAlignment: CrossAxisAlignment.center,
+ this.textDirection,
+ this.verticalDirection: VerticalDirection.down,
this.textBaseline,
List<Widget> children: const <Widget>[],
}) : assert(direction != null),
assert(mainAxisAlignment != null),
assert(mainAxisSize != null),
assert(crossAxisAlignment != null),
- assert(crossAxisAlignment != CrossAxisAlignment.baseline || textBaseline != null),// https://github.com/dart-lang/sdk/issues/29278
+ assert(verticalDirection != null),
+ assert(crossAxisAlignment != CrossAxisAlignment.baseline || textBaseline != null),
super(key: key, children: children);
/// The direction to use as the main axis.
@@ -2546,9 +2564,88 @@
/// children in the cross axis (e.g., horizontally for a [Column]).
final CrossAxisAlignment crossAxisAlignment;
+ /// Determines the order to lay children out horizontally and how to interpret
+ /// `start` and `end` in the horizontal direction.
+ ///
+ /// Defaults to the ambient [Directionality].
+ ///
+ /// If the [direction] is [Axis.horizontal], this controls which order
+ /// children are painted in (left-to-right or right-to-left), and the meaning
+ /// of the [mainAxisAlignment] property's [MainAxisAlignment.start] and
+ /// [MainAxisAlignment.end] values.
+ ///
+ /// If the [direction] is [Axis.horizontal], and either the
+ /// [mainAxisAlignment] is either [MainAxisAlignment.start] or
+ /// [MainAxisAlignment.end], or there's more than one child, then the
+ /// [textDirection] (or the ambient [Directionality]) must not be null.
+ ///
+ /// If the [direction] is [Axis.vertical], this controls the meaning of the
+ /// [crossAxisAlignment] property's [CrossAxisAlignment.start] and
+ /// [CrossAxisAlignment.end] values.
+ ///
+ /// If the [direction] is [Axis.vertical], and the [crossAxisAlignment] is
+ /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the
+ /// [textDirection] (or the ambient [Directionality]) must not be null.
+ final TextDirection textDirection;
+
+ /// Determines the order to lay children out vertically and how to interpret
+ /// `start` and `end` in the vertical direction.
+ ///
+ /// Defaults to [VerticalDirection.down].
+ ///
+ /// If the [direction] is [Axis.vertical], this controls which order children
+ /// are painted in (down or up), the meaning of the [mainAxisAlignment]
+ /// property's [MainAxisAlignment.start] and [MainAxisAlignment.end] values.
+ ///
+ /// If the [direction] is [Axis.vertical], and either the [mainAxisAlignment]
+ /// is either [MainAxisAlignment.start] or [MainAxisAlignment.end], or there's
+ /// more than one child, then the [verticalDirection] must not be null.
+ ///
+ /// If the [direction] is [Axis.horizontal], this controls the meaning of the
+ /// [crossAxisAlignment] property's [CrossAxisAlignment.start] and
+ /// [CrossAxisAlignment.end] values.
+ ///
+ /// If the [direction] is [Axis.horizontal], and the [crossAxisAlignment] is
+ /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the
+ /// [verticalDirection] must not be null.
+ final VerticalDirection verticalDirection;
+
/// If aligning items according to their baseline, which baseline to use.
final TextBaseline textBaseline;
+ bool get _needTextDirection {
+ assert(direction != null);
+ switch (direction) {
+ case Axis.horizontal:
+ return true; // because it affects the layout order.
+ case Axis.vertical:
+ assert(crossAxisAlignment != null);
+ return crossAxisAlignment == CrossAxisAlignment.start
+ || crossAxisAlignment == CrossAxisAlignment.end;
+ }
+ return null;
+ }
+
+ /// The value to pass to [RenderFlex.textDirection].
+ ///
+ /// This value is derived from the [textDirection] property and the ambient
+ /// [Directionality]. The value is null if there is no need to specify the
+ /// text direction. In practice there's always a need to specify the direction
+ /// except for vertical flexes (e.g. [Column]s) whose [crossAxisAlignment] is
+ /// not dependent on the text direction (not `start` or `end`). In particular,
+ /// a [Row] always needs a text direction because the text direction controls
+ /// its layout order. (For [Column]s, the layout order is controlled by
+ /// [verticalDirection], which is always specified as it does not depend on an
+ /// inherited widget and defaults to [VerticalDirection.down].)
+ ///
+ /// This method exists so that subclasses of [Flex] that create their own
+ /// render objects that are derived from [RenderFlex] can do so and still use
+ /// the logic for providing a text direction only when it is necessary.
+ @protected
+ TextDirection getEffectiveTextDirection(BuildContext context) {
+ return textDirection ?? (_needTextDirection ? Directionality.of(context) : null);
+ }
+
@override
RenderFlex createRenderObject(BuildContext context) {
return new RenderFlex(
@@ -2556,7 +2653,9 @@
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: mainAxisSize,
crossAxisAlignment: crossAxisAlignment,
- textBaseline: textBaseline
+ textDirection: getEffectiveTextDirection(context),
+ verticalDirection: verticalDirection,
+ textBaseline: textBaseline,
);
}
@@ -2567,8 +2666,22 @@
..mainAxisAlignment = mainAxisAlignment
..mainAxisSize = mainAxisSize
..crossAxisAlignment = crossAxisAlignment
+ ..textDirection = getEffectiveTextDirection(context)
+ ..verticalDirection = verticalDirection
..textBaseline = textBaseline;
}
+
+ @override
+ void debugFillProperties(DiagnosticPropertiesBuilder description) {
+ super.debugFillProperties(description);
+ description.add(new EnumProperty<Axis>('direction', direction));
+ description.add(new EnumProperty<MainAxisAlignment>('mainAxisAlignment', mainAxisAlignment));
+ description.add(new EnumProperty<MainAxisSize>('mainAxisSize', mainAxisSize, defaultValue: MainAxisSize.max));
+ description.add(new EnumProperty<CrossAxisAlignment>('crossAxisAlignment', crossAxisAlignment));
+ description.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+ description.add(new EnumProperty<VerticalDirection>('verticalDirection', verticalDirection, defaultValue: VerticalDirection.down));
+ description.add(new EnumProperty<TextBaseline>('textBaseline', textBaseline, defaultValue: null));
+ }
}
/// A widget that displays its children in a horizontal array.
@@ -2716,14 +2829,24 @@
class Row extends Flex {
/// Creates a horizontal array of children.
///
- /// The [direction], [mainAxisAlignment], [mainAxisSize], and
- /// [crossAxisAlignment] arguments must not be null. If [crossAxisAlignment]
- /// is [CrossAxisAlignment.baseline], then [textBaseline] must not be null.
+ /// The [direction], [mainAxisAlignment], [mainAxisSize],
+ /// [crossAxisAlignment], and [verticalDirection] arguments must not be null.
+ /// If [crossAxisAlignment] is [CrossAxisAlignment.baseline], then
+ /// [textBaseline] must not be null.
+ ///
+ /// The [textDirection] argument defaults to the ambient [Directionality], if
+ /// any. If there is no ambient directionality, and a text direction is going
+ /// to be necessary to determine the layout order (which is always the case
+ /// unless the row has no children or only one child) or to disambiguate
+ /// `start` or `end` values for the [mainAxisDirection], the [textDirection]
+ /// must not be null.
Row({
Key key,
MainAxisAlignment mainAxisAlignment: MainAxisAlignment.start,
MainAxisSize mainAxisSize: MainAxisSize.max,
CrossAxisAlignment crossAxisAlignment: CrossAxisAlignment.center,
+ TextDirection textDirection,
+ VerticalDirection verticalDirection: VerticalDirection.down,
TextBaseline textBaseline,
List<Widget> children: const <Widget>[],
}) : super(
@@ -2733,7 +2856,9 @@
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: mainAxisSize,
crossAxisAlignment: crossAxisAlignment,
- textBaseline: textBaseline
+ textDirection: textDirection,
+ verticalDirection: verticalDirection,
+ textBaseline: textBaseline,
);
}
@@ -2843,14 +2968,22 @@
class Column extends Flex {
/// Creates a vertical array of children.
///
- /// The [direction], [mainAxisAlignment], [mainAxisSize], and
- /// [crossAxisAlignment] arguments must not be null. If [crossAxisAlignment]
- /// is [CrossAxisAlignment.baseline], then [textBaseline] must not be null.
+ /// The [direction], [mainAxisAlignment], [mainAxisSize],
+ /// [crossAxisAlignment], and [verticalDirection] arguments must not be null.
+ /// If [crossAxisAlignment] is [CrossAxisAlignment.baseline], then
+ /// [textBaseline] must not be null.
+ ///
+ /// The [textDirection] argument defaults to the ambient [Directionality], if
+ /// any. If there is no ambient directionality, and a text direction is going
+ /// to be necessary to disambiguate `start` or `end` values for the
+ /// [crossAxisDirection], the [textDirection] must not be null.
Column({
Key key,
MainAxisAlignment mainAxisAlignment: MainAxisAlignment.start,
MainAxisSize mainAxisSize: MainAxisSize.max,
CrossAxisAlignment crossAxisAlignment: CrossAxisAlignment.center,
+ TextDirection textDirection,
+ VerticalDirection verticalDirection: VerticalDirection.down,
TextBaseline textBaseline,
List<Widget> children: const <Widget>[],
}) : super(
@@ -2860,7 +2993,9 @@
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: mainAxisSize,
crossAxisAlignment: crossAxisAlignment,
- textBaseline: textBaseline
+ textDirection: textDirection,
+ verticalDirection: verticalDirection,
+ textBaseline: textBaseline,
);
}
diff --git a/packages/flutter/lib/src/widgets/container.dart b/packages/flutter/lib/src/widgets/container.dart
index 953311e..b0ebe09 100644
--- a/packages/flutter/lib/src/widgets/container.dart
+++ b/packages/flutter/lib/src/widgets/container.dart
@@ -282,7 +282,7 @@
/// Empty space to inscribe inside the [decoration]. The [child], if any, is
/// placed inside this padding.
- final EdgeInsets padding;
+ final EdgeInsetsGeometry padding;
/// The decoration to paint behind the [child].
///
@@ -303,18 +303,18 @@
final BoxConstraints constraints;
/// Empty space to surround the [decoration] and [child].
- final EdgeInsets margin;
+ final EdgeInsetsGeometry margin;
/// The transformation matrix to apply before painting the container.
final Matrix4 transform;
- EdgeInsets get _paddingIncludingDecoration {
+ EdgeInsetsGeometry get _paddingIncludingDecoration {
if (decoration == null || decoration.padding == null)
return padding;
- final EdgeInsets decorationPadding = decoration.padding;
+ final EdgeInsetsGeometry decorationPadding = decoration.padding;
if (padding == null)
return decorationPadding;
- return padding + decorationPadding;
+ return padding.add(decorationPadding);
}
@override
@@ -332,7 +332,7 @@
if (alignment != null)
current = new Align(alignment: alignment, child: current);
- final EdgeInsets effectivePadding = _paddingIncludingDecoration;
+ final EdgeInsetsGeometry effectivePadding = _paddingIncludingDecoration;
if (effectivePadding != null)
current = new Padding(padding: effectivePadding, child: current);
@@ -363,11 +363,11 @@
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
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>('fg', foregroundDecoration, 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));
}
}
diff --git a/packages/flutter/lib/src/widgets/localizations.dart b/packages/flutter/lib/src/widgets/localizations.dart
index 9b63985..961fbc6 100644
--- a/packages/flutter/lib/src/widgets/localizations.dart
+++ b/packages/flutter/lib/src/widgets/localizations.dart
@@ -7,6 +7,7 @@
import 'package:flutter/foundation.dart';
+import 'basic.dart';
import 'binding.dart';
import 'container.dart';
import 'framework.dart';
@@ -90,6 +91,47 @@
/// rebuilt. If it returns true then dependent widgets will be rebuilt
/// after [load] has completed.
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 {
@@ -158,7 +200,7 @@
///
/// This class is effectively an [InheritedWidget]. If it's rebuilt with
/// 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
/// `Localizations.of(context)` will be rebuilt after the resources
/// for the new locale have been loaded.
@@ -199,21 +241,25 @@
/// One could choose another approach for loading localized resources and looking them up while
/// still conforming to the structure of this example.
class Localizations extends StatefulWidget {
+ /// Create a widget from which ambient localizations (translated strings)
+ /// can be obtained.
Localizations({
Key key,
@required this.locale,
- this.delegates,
- this.child
- }) : super(key: key) {
- assert(locale != null);
+ @required this.delegates,
+ this.child,
+ }) : 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.
final Locale locale;
/// This list collectively defines the localized resources objects that can
- /// be retrieved with [ Localizations.of].
- final Iterable<LocalizationsDelegate<dynamic>> delegates;
+ /// be retrieved with [Localizations.of].
+ final List<LocalizationsDelegate<dynamic>> delegates;
/// The widget below this widget in the tree.
final Widget child;
@@ -247,6 +293,13 @@
@override
_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> {
@@ -329,18 +382,29 @@
T resourcesFor<T>(Type type) {
assert(type != null);
- final dynamic resources = _typeToResources[type];
+ final T resources = _typeToResources[type];
assert(resources.runtimeType == type);
return resources;
}
+ TextDirection get _textDirection {
+ final WidgetsLocalizations resources = _typeToResources[WidgetsLocalizations];
+ assert(resources != null);
+ return resources.textDirection;
+ }
+
@override
Widget build(BuildContext context) {
+ if (_locale == null)
+ return new Container();
return new _LocalizationsScope(
key: _localizedResourcesScopeKey,
- locale: widget.locale,
+ locale: _locale,
localizationsState: this,
- child: _locale != null ? widget.child : new Container(),
+ child: new Directionality(
+ textDirection: _textDirection,
+ child: widget.child,
+ ),
);
}
}
diff --git a/packages/flutter/lib/src/widgets/scroll_controller.dart b/packages/flutter/lib/src/widgets/scroll_controller.dart
index b4b0789..50476b8 100644
--- a/packages/flutter/lib/src/widgets/scroll_controller.dart
+++ b/packages/flutter/lib/src/widgets/scroll_controller.dart
@@ -264,7 +264,7 @@
description.add('no clients');
} else if (_positions.length == 1) {
// 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 {
description.add('${_positions.length} clients');
}
diff --git a/packages/flutter/test/cupertino/bottom_tab_bar_test.dart b/packages/flutter/test/cupertino/bottom_tab_bar_test.dart
index 9005414..db765c2 100644
--- a/packages/flutter/test/cupertino/bottom_tab_bar_test.dart
+++ b/packages/flutter/test/cupertino/bottom_tab_bar_test.dart
@@ -7,10 +7,19 @@
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() {
testWidgets('Need at least 2 tabs', (WidgetTester tester) async {
try {
- await tester.pumpWidget(new CupertinoTabBar(
+ await pumpWidgetWithBoilerplate(tester, new CupertinoTabBar(
items: <BottomNavigationBarItem>[
const BottomNavigationBarItem(
icon: const ImageIcon(const TestImageProvider(24, 24)),
@@ -19,13 +28,14 @@
],
));
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.
}
});
testWidgets('Active and inactive colors', (WidgetTester tester) async {
- await tester.pumpWidget(new CupertinoTabBar(
+ await pumpWidgetWithBoilerplate(tester, new CupertinoTabBar(
items: <BottomNavigationBarItem>[
const BottomNavigationBarItem(
icon: const ImageIcon(const TestImageProvider(24, 24)),
@@ -55,7 +65,7 @@
});
testWidgets('Opaque background does not add blur effects', (WidgetTester tester) async {
- await tester.pumpWidget(new CupertinoTabBar(
+ await pumpWidgetWithBoilerplate(tester, new CupertinoTabBar(
items: <BottomNavigationBarItem>[
const BottomNavigationBarItem(
icon: const ImageIcon(const TestImageProvider(24, 24)),
@@ -70,7 +80,7 @@
expect(find.byType(BackdropFilter), findsOneWidget);
- await tester.pumpWidget(new CupertinoTabBar(
+ await pumpWidgetWithBoilerplate(tester, new CupertinoTabBar(
items: <BottomNavigationBarItem>[
const BottomNavigationBarItem(
icon: const ImageIcon(const TestImageProvider(24, 24)),
@@ -90,7 +100,7 @@
testWidgets('Tap callback', (WidgetTester tester) async {
int callbackTab;
- await tester.pumpWidget(new CupertinoTabBar(
+ await pumpWidgetWithBoilerplate(tester, new CupertinoTabBar(
items: <BottomNavigationBarItem>[
const BottomNavigationBarItem(
icon: const ImageIcon(const TestImageProvider(24, 24)),
diff --git a/packages/flutter/test/material/about_test.dart b/packages/flutter/test/material/about_test.dart
index f2b9fe0..223e1bb 100644
--- a/packages/flutter/test/material/about_test.dart
+++ b/packages/flutter/test/material/about_test.dart
@@ -67,7 +67,10 @@
testWidgets('About box logic defaults to executable name for app name', (WidgetTester tester) async {
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);
});
diff --git a/packages/flutter/test/material/app_bar_test.dart b/packages/flutter/test/material/app_bar_test.dart
index 298fa0d..8ad2c3e 100644
--- a/packages/flutter/test/material/app_bar_test.dart
+++ b/packages/flutter/test/material/app_bar_test.dart
@@ -8,31 +8,34 @@
import 'package:flutter_test/flutter_test.dart';
Widget buildSliverAppBarApp({ bool floating, bool pinned, double expandedHeight, bool snap: false }) {
- return new MediaQuery(
- data: const MediaQueryData(),
- child: new Scaffold(
- body: new DefaultTabController(
- length: 3,
- child: new CustomScrollView(
- primary: true,
- slivers: <Widget>[
- new SliverAppBar(
- title: const Text('AppBar Title'),
- floating: floating,
- pinned: pinned,
- expandedHeight: expandedHeight,
- snap: snap,
- bottom: new TabBar(
- tabs: <String>['A','B','C'].map((String t) => new Tab(text: 'TAB $t')).toList(),
+ return new Directionality(
+ textDirection: TextDirection.ltr,
+ child: new MediaQuery(
+ data: const MediaQueryData(),
+ child: new Scaffold(
+ body: new DefaultTabController(
+ length: 3,
+ child: new CustomScrollView(
+ primary: true,
+ slivers: <Widget>[
+ new SliverAppBar(
+ title: const Text('AppBar Title'),
+ floating: floating,
+ pinned: pinned,
+ expandedHeight: expandedHeight,
+ snap: snap,
+ bottom: new TabBar(
+ tabs: <String>['A','B','C'].map((String t) => new Tab(text: 'TAB $t')).toList(),
+ ),
),
- ),
- new SliverToBoxAdapter(
- child: new Container(
- height: 1200.0,
- color: Colors.orange[400],
+ new SliverToBoxAdapter(
+ child: new Container(
+ height: 1200.0,
+ color: Colors.orange[400],
+ ),
),
- ),
- ],
+ ],
+ ),
),
),
),
@@ -699,11 +702,14 @@
const MediaQueryData topPadding100 = const MediaQueryData(padding: const EdgeInsets.only(top: 100.0));
await tester.pumpWidget(
- new MediaQuery(
- data: topPadding100,
- child: new Scaffold(
- primary: false,
- appBar: new AppBar(),
+ new Directionality(
+ textDirection: TextDirection.ltr,
+ child: new MediaQuery(
+ data: topPadding100,
+ child: new Scaffold(
+ primary: false,
+ appBar: new AppBar(),
+ ),
),
),
);
@@ -711,11 +717,14 @@
expect(appBarHeight(tester), kToolbarHeight);
await tester.pumpWidget(
- new MediaQuery(
- data: topPadding100,
- child: new Scaffold(
- primary: true,
- appBar: new AppBar(title: const Text('title'))
+ new Directionality(
+ textDirection: TextDirection.ltr,
+ child: new MediaQuery(
+ data: topPadding100,
+ child: new Scaffold(
+ primary: true,
+ appBar: new AppBar(title: const Text('title'))
+ ),
),
),
);
@@ -724,14 +733,17 @@
expect(appBarHeight(tester), kToolbarHeight + 100.0);
await tester.pumpWidget(
- new MediaQuery(
- data: topPadding100,
- child: new Scaffold(
- primary: false,
- appBar: new AppBar(
- bottom: new PreferredSize(
- preferredSize: const Size.fromHeight(200.0),
- child: new Container(),
+ new Directionality(
+ textDirection: TextDirection.ltr,
+ child: new MediaQuery(
+ data: topPadding100,
+ child: new Scaffold(
+ primary: false,
+ appBar: new AppBar(
+ bottom: new PreferredSize(
+ preferredSize: const Size.fromHeight(200.0),
+ child: new Container(),
+ ),
),
),
),
@@ -741,14 +753,17 @@
expect(appBarHeight(tester), kToolbarHeight + 200.0);
await tester.pumpWidget(
- new MediaQuery(
- data: topPadding100,
- child: new Scaffold(
- primary: true,
- appBar: new AppBar(
- bottom: new PreferredSize(
- preferredSize: const Size.fromHeight(200.0),
- child: new Container(),
+ new Directionality(
+ textDirection: TextDirection.ltr,
+ child: new MediaQuery(
+ data: topPadding100,
+ child: new Scaffold(
+ primary: true,
+ appBar: new AppBar(
+ bottom: new PreferredSize(
+ preferredSize: const Size.fromHeight(200.0),
+ child: new Container(),
+ ),
),
),
),
@@ -758,11 +773,14 @@
expect(appBarHeight(tester), kToolbarHeight + 100.0 + 200.0);
await tester.pumpWidget(
- new MediaQuery(
- data: topPadding100,
- child: new AppBar(
- primary: false,
- title: const Text('title'),
+ new Directionality(
+ textDirection: TextDirection.ltr,
+ child: new MediaQuery(
+ data: topPadding100,
+ child: new AppBar(
+ primary: false,
+ title: const Text('title'),
+ ),
),
),
);
diff --git a/packages/flutter/test/material/button_bar_test.dart b/packages/flutter/test/material/button_bar_test.dart
index 37d9339..f4f4226 100644
--- a/packages/flutter/test/material/button_bar_test.dart
+++ b/packages/flutter/test/material/button_bar_test.dart
@@ -6,7 +6,12 @@
import 'package:flutter_test/flutter_test.dart';
void main() {
- testWidgets('ButtonBar default control', (WidgetTester tester) async {
- await tester.pumpWidget(const Center(child: const ButtonBar()));
+ testWidgets('ButtonBar default control smoketest', (WidgetTester tester) async {
+ await tester.pumpWidget(
+ new Directionality(
+ textDirection: TextDirection.ltr,
+ child: const ButtonBar(),
+ ),
+ );
});
}
diff --git a/packages/flutter/test/material/chip_test.dart b/packages/flutter/test/material/chip_test.dart
index 606995d..092e2f7 100644
--- a/packages/flutter/test/material/chip_test.dart
+++ b/packages/flutter/test/material/chip_test.dart
@@ -107,29 +107,31 @@
feedback.dispose();
});
- testWidgets(
- 'Chip does not constrain size of label widget if it does not exceed '
- 'the available space', (WidgetTester tester) async {
+ testWidgets('Chip does not constrain size of label widget if it does not exceed '
+ 'the available space', (WidgetTester tester) async {
const double labelWidth = 50.0;
const double labelHeight = 30.0;
final Key labelKey = new UniqueKey();
await tester.pumpWidget(
- new Material(
- child: new Center(
- child: new Container(
- width: 500.0,
- height: 500.0,
- child: new Column(
- children: <Widget>[
- new Chip(
- label: new Container(
- key: labelKey,
- width: labelWidth,
- height: labelHeight,
+ new Directionality(
+ textDirection: TextDirection.ltr,
+ child: new Material(
+ child: new Center(
+ child: new Container(
+ width: 500.0,
+ height: 500.0,
+ child: new Column(
+ children: <Widget>[
+ new Chip(
+ label: new Container(
+ key: labelKey,
+ width: labelWidth,
+ height: labelHeight,
+ ),
),
- ),
- ],
+ ],
+ ),
),
),
),
@@ -141,15 +143,13 @@
expect(labelSize.height, labelHeight);
});
- testWidgets(
- 'Chip constrains the size of the label widget when it exceeds the '
- 'available space', (WidgetTester tester) async {
+ testWidgets('Chip constrains the size of the label widget when it exceeds the '
+ 'available space', (WidgetTester tester) async {
await _testConstrainedLabel(tester);
});
- testWidgets(
- 'Chip constrains the size of the label widget when it exceeds the '
- 'available space and the avatar is present', (WidgetTester tester) async {
+ testWidgets('Chip constrains the size of the label widget when it exceeds the '
+ 'available space and the avatar is present', (WidgetTester tester) async {
await _testConstrainedLabel(
tester,
avatar: const CircleAvatar(
@@ -158,20 +158,16 @@
);
});
- testWidgets(
- 'Chip constrains the size of the label widget when it exceeds the '
- 'available space and the delete icon is present',
- (WidgetTester tester) async {
+ testWidgets('Chip constrains the size of the label widget when it exceeds the '
+ 'available space and the delete icon is present', (WidgetTester tester) async {
await _testConstrainedLabel(
tester,
onDeleted: () {},
);
});
- testWidgets(
- '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 {
+ testWidgets('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 {
await _testConstrainedLabel(
tester,
avatar: const CircleAvatar(
@@ -223,4 +219,104 @@
expect(tester.getSize(find.byType(Text)), const Size(40.0, 10.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));
+ });
}
diff --git a/packages/flutter/test/material/control_list_tile_test.dart b/packages/flutter/test/material/control_list_tile_test.dart
index b7697e4..3550ea0 100644
--- a/packages/flutter/test/material/control_list_tile_test.dart
+++ b/packages/flutter/test/material/control_list_tile_test.dart
@@ -10,10 +10,17 @@
import '../widgets/semantics_tester.dart';
+Widget wrap({ Widget child }) {
+ return new Directionality(
+ textDirection: TextDirection.ltr,
+ child: new Material(child: child),
+ );
+}
+
void main() {
testWidgets('CheckboxListTile control test', (WidgetTester tester) async {
final List<dynamic> log = <dynamic>[];
- await tester.pumpWidget(new Material(
+ await tester.pumpWidget(wrap(
child: new CheckboxListTile(
value: true,
onChanged: (bool value) { log.add(value); },
@@ -28,7 +35,7 @@
testWidgets('RadioListTile control test', (WidgetTester tester) async {
final List<dynamic> log = <dynamic>[];
- await tester.pumpWidget(new Material(
+ await tester.pumpWidget(wrap(
child: new RadioListTile<bool>(
value: true,
groupValue: false,
@@ -44,7 +51,7 @@
testWidgets('SwitchListTile control test', (WidgetTester tester) async {
final List<dynamic> log = <dynamic>[];
- await tester.pumpWidget(new Material(
+ await tester.pumpWidget(wrap(
child: new SwitchListTile(
value: true,
onChanged: (bool value) { log.add(value); },
@@ -59,7 +66,7 @@
testWidgets('SwitchListTile control test', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
- await tester.pumpWidget(new Material(
+ await tester.pumpWidget(wrap(
child: new Column(
children: <Widget>[
new SwitchListTile(
diff --git a/packages/flutter/test/material/expansion_panel_test.dart b/packages/flutter/test/material/expansion_panel_test.dart
index e474e0f..64e16ab 100644
--- a/packages/flutter/test/material/expansion_panel_test.dart
+++ b/packages/flutter/test/material/expansion_panel_test.dart
@@ -11,22 +11,24 @@
bool isExpanded;
await tester.pumpWidget(
- new SingleChildScrollView(
- child: new ExpansionPanelList(
- expansionCallback: (int _index, bool _isExpanded) {
- index = _index;
- isExpanded = _isExpanded;
- },
- children: <ExpansionPanel>[
- new ExpansionPanel(
- headerBuilder: (BuildContext context, bool isExpanded) {
- return new Text(isExpanded ? 'B' : 'A');
- },
- body: const SizedBox(height: 100.0)
- )
- ]
- )
- )
+ new MaterialApp(
+ home: new SingleChildScrollView(
+ child: new ExpansionPanelList(
+ expansionCallback: (int _index, bool _isExpanded) {
+ index = _index;
+ isExpanded = _isExpanded;
+ },
+ children: <ExpansionPanel>[
+ new ExpansionPanel(
+ headerBuilder: (BuildContext context, bool isExpanded) {
+ return new Text(isExpanded ? 'B' : 'A');
+ },
+ body: const SizedBox(height: 100.0),
+ ),
+ ],
+ ),
+ ),
+ ),
);
expect(find.text('A'), findsOneWidget);
@@ -42,28 +44,28 @@
// now expand the child panel
await tester.pumpWidget(
- new SingleChildScrollView(
- child: new ExpansionPanelList(
- expansionCallback: (int _index, bool _isExpanded) {
- index = _index;
- isExpanded = _isExpanded;
- },
- children: <ExpansionPanel>[
- new ExpansionPanel(
- headerBuilder: (BuildContext context, bool isExpanded) {
- return new Text(isExpanded ? 'B' : 'A');
- },
- body: const SizedBox(height: 100.0),
- isExpanded: true // this is the addition
- )
- ]
- )
- )
+ new MaterialApp(
+ home: new SingleChildScrollView(
+ child: new ExpansionPanelList(
+ expansionCallback: (int _index, bool _isExpanded) {
+ index = _index;
+ isExpanded = _isExpanded;
+ },
+ children: <ExpansionPanel>[
+ new ExpansionPanel(
+ headerBuilder: (BuildContext context, bool isExpanded) {
+ return new Text(isExpanded ? 'B' : 'A');
+ },
+ body: const SizedBox(height: 100.0),
+ isExpanded: true, // this is the addition
+ ),
+ ],
+ ),
+ ),
+ ),
);
-
await tester.pump(const Duration(milliseconds: 200));
-
expect(find.text('A'), findsNothing);
expect(find.text('B'), findsOneWidget);
box = tester.renderObject(find.byType(ExpansionPanelList));
diff --git a/packages/flutter/test/material/grid_title_test.dart b/packages/flutter/test/material/grid_title_test.dart
index b072303..5cf53a6 100644
--- a/packages/flutter/test/material/grid_title_test.dart
+++ b/packages/flutter/test/material/grid_title_test.dart
@@ -10,23 +10,25 @@
final Key headerKey = new UniqueKey();
final Key footerKey = new UniqueKey();
- await tester.pumpWidget(new GridTile(
- header: new GridTileBar(
- key: headerKey,
- leading: const Icon(Icons.thumb_up),
- title: const Text('Header'),
- subtitle: const Text('Subtitle'),
- trailing: const Icon(Icons.thumb_up),
- ),
- child: new DecoratedBox(
- decoration: new BoxDecoration(
- color: Colors.green[500],
+ await tester.pumpWidget(new MaterialApp(
+ home: new GridTile(
+ header: new GridTileBar(
+ key: headerKey,
+ leading: const Icon(Icons.thumb_up),
+ title: const Text('Header'),
+ subtitle: const Text('Subtitle'),
+ trailing: const Icon(Icons.thumb_up),
),
- ),
- footer: new GridTileBar(
- key: footerKey,
- title: const Text('Footer'),
- backgroundColor: Colors.black38,
+ child: new DecoratedBox(
+ decoration: new BoxDecoration(
+ color: Colors.green[500],
+ ),
+ ),
+ footer: new GridTileBar(
+ key: footerKey,
+ title: const Text('Footer'),
+ backgroundColor: Colors.black38,
+ ),
),
));
diff --git a/packages/flutter/test/material/icon_button_test.dart b/packages/flutter/test/material/icon_button_test.dart
index 1d9ed34..6d7a2f5 100644
--- a/packages/flutter/test/material/icon_button_test.dart
+++ b/packages/flutter/test/material/icon_button_test.dart
@@ -98,15 +98,18 @@
'test default icon buttons can be stretched if specified',
(WidgetTester tester) async {
await tester.pumpWidget(
- new Material(
- child: new Row(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: <Widget> [
- new IconButton(
- onPressed: mockOnPressedFunction,
- icon: const Icon(Icons.ac_unit),
- ),
- ],
+ new Directionality(
+ textDirection: TextDirection.ltr,
+ child: new Material(
+ child: new Row(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: <Widget> [
+ new IconButton(
+ onPressed: mockOnPressedFunction,
+ icon: const Icon(Icons.ac_unit),
+ ),
+ ],
+ ),
),
),
);
diff --git a/packages/flutter/test/material/input_decorator_test.dart b/packages/flutter/test/material/input_decorator_test.dart
index 81e41a1..49c323b 100644
--- a/packages/flutter/test/material/input_decorator_test.dart
+++ b/packages/flutter/test/material/input_decorator_test.dart
@@ -9,37 +9,43 @@
testWidgets('InputDecorator always expands horizontally', (WidgetTester tester) async {
final Key key = new UniqueKey();
- await tester.pumpWidget(new Material(
- child: new Center(
- child: new InputDecorator(
- decoration: const InputDecoration(),
- child: new Container(key: key, width: 50.0, height: 60.0, color: Colors.blue),
+ await tester.pumpWidget(new MaterialApp(
+ home: new Material(
+ child: new Center(
+ child: new InputDecorator(
+ 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)));
- await tester.pumpWidget(new Material(
- child: new Center(
- child: new InputDecorator(
- decoration: const InputDecoration(
- icon: const Icon(Icons.add_shopping_cart),
+ await tester.pumpWidget(new MaterialApp(
+ home: new Material(
+ child: new Center(
+ child: new InputDecorator(
+ 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)));
- await tester.pumpWidget(new Material(
- child: new Center(
- child: new InputDecorator(
- decoration: const InputDecoration.collapsed(
- hintText: 'Hint text',
+ await tester.pumpWidget(new MaterialApp(
+ home: new Material(
+ child: new Center(
+ child: new InputDecorator(
+ 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),
),
),
));
diff --git a/packages/flutter/test/material/page_selector_test.dart b/packages/flutter/test/material/page_selector_test.dart
index ca1a65e..8abe971 100644
--- a/packages/flutter/test/material/page_selector_test.dart
+++ b/packages/flutter/test/material/page_selector_test.dart
@@ -9,32 +9,35 @@
const Color kUnselectedColor = Colors.transparent;
Widget buildFrame(TabController tabController, { Color color, Color selectedColor, double indicatorSize: 12.0 }) {
- return new Theme(
- data: new ThemeData(accentColor: kSelectedColor),
- child: new SizedBox.expand(
- child: new Center(
- child: new SizedBox(
- width: 400.0,
- height: 400.0,
- child: new Column(
- children: <Widget>[
- new TabPageSelector(
- controller: tabController,
- color: color,
- selectedColor: selectedColor,
- indicatorSize: indicatorSize,
- ),
- new Flexible(
- child: new TabBarView(
+ return new Directionality(
+ textDirection: TextDirection.ltr,
+ child: new Theme(
+ data: new ThemeData(accentColor: kSelectedColor),
+ child: new SizedBox.expand(
+ child: new Center(
+ child: new SizedBox(
+ width: 400.0,
+ height: 400.0,
+ child: new Column(
+ children: <Widget>[
+ new TabPageSelector(
controller: tabController,
- children: <Widget>[
- const Center(child: const Text('0')),
- const Center(child: const Text('1')),
- const Center(child: const Text('2')),
- ],
+ color: color,
+ selectedColor: selectedColor,
+ indicatorSize: indicatorSize,
),
- ),
- ],
+ 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')),
+ ],
+ ),
+ ),
+ ],
+ ),
),
),
),
diff --git a/packages/flutter/test/material/stepper_test.dart b/packages/flutter/test/material/stepper_test.dart
index fa552eb..88cb72a 100644
--- a/packages/flutter/test/material/stepper_test.dart
+++ b/packages/flutter/test/material/stepper_test.dart
@@ -10,29 +10,31 @@
int index = 0;
await tester.pumpWidget(
- new Material(
- child: new Stepper(
- onStepTapped: (int i) {
- index = i;
- },
- steps: <Step>[
- const Step(
- title: const Text('Step 1'),
- content: const SizedBox(
- width: 100.0,
- height: 100.0
- )
- ),
- const Step(
- title: const Text('Step 2'),
- content: const SizedBox(
- width: 100.0,
- height: 100.0
- )
- )
- ]
- )
- )
+ new MaterialApp(
+ home: new Material(
+ child: new Stepper(
+ onStepTapped: (int i) {
+ index = i;
+ },
+ steps: <Step>[
+ const Step(
+ title: const Text('Step 1'),
+ content: const SizedBox(
+ width: 100.0,
+ height: 100.0,
+ ),
+ ),
+ const Step(
+ title: const Text('Step 2'),
+ content: const SizedBox(
+ width: 100.0,
+ height: 100.0,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
);
await tester.tap(find.text('Step 2'));
expect(index, 1);
@@ -40,57 +42,61 @@
testWidgets('Stepper expansion test', (WidgetTester tester) async {
await tester.pumpWidget(
- new Center(
- child: new Material(
- child: new Stepper(
- steps: <Step>[
- const Step(
- title: const Text('Step 1'),
- content: const SizedBox(
- width: 100.0,
- height: 100.0
- )
- ),
- const Step(
- title: const Text('Step 2'),
- content: const SizedBox(
- width: 200.0,
- height: 200.0
- )
- )
- ]
- )
- )
- )
+ new MaterialApp(
+ home: new Center(
+ child: new Material(
+ child: new Stepper(
+ steps: <Step>[
+ const Step(
+ title: const Text('Step 1'),
+ content: const SizedBox(
+ width: 100.0,
+ height: 100.0,
+ ),
+ ),
+ const Step(
+ title: const Text('Step 2'),
+ content: const SizedBox(
+ width: 200.0,
+ height: 200.0,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
);
RenderBox box = tester.renderObject(find.byType(Stepper));
expect(box.size.height, 332.0);
await tester.pumpWidget(
- new Center(
- child: new Material(
- child: new Stepper(
- currentStep: 1,
- steps: <Step>[
- const Step(
- title: const Text('Step 1'),
- content: const SizedBox(
- width: 100.0,
- height: 100.0
- )
- ),
- const Step(
- title: const Text('Step 2'),
- content: const SizedBox(
- width: 200.0,
- height: 200.0
- )
- )
- ]
- )
- )
- )
+ new MaterialApp(
+ home: new Center(
+ child: new Material(
+ child: new Stepper(
+ currentStep: 1,
+ steps: <Step>[
+ const Step(
+ title: const Text('Step 1'),
+ content: const SizedBox(
+ width: 100.0,
+ height: 100.0,
+ ),
+ ),
+ const Step(
+ title: const Text('Step 2'),
+ content: const SizedBox(
+ width: 200.0,
+ height: 200.0,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
);
await tester.pump(const Duration(milliseconds: 100));
@@ -103,22 +109,24 @@
testWidgets('Stepper horizontal size test', (WidgetTester tester) async {
await tester.pumpWidget(
- new Center(
- child: new Material(
- child: new Stepper(
- type: StepperType.horizontal,
- steps: <Step>[
- const Step(
- title: const Text('Step 1'),
- content: const SizedBox(
- width: 100.0,
- height: 100.0
- )
- )
- ]
- )
- )
- )
+ new MaterialApp(
+ home: new Center(
+ child: new Material(
+ child: new Stepper(
+ type: StepperType.horizontal,
+ steps: <Step>[
+ const Step(
+ title: const Text('Step 1'),
+ content: const SizedBox(
+ width: 100.0,
+ height: 100.0,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
);
final RenderBox box = tester.renderObject(find.byType(Stepper));
@@ -127,43 +135,47 @@
testWidgets('Stepper visibility test', (WidgetTester tester) async {
await tester.pumpWidget(
- new Material(
- child: new Stepper(
- type: StepperType.horizontal,
- steps: <Step>[
- const Step(
- title: const Text('Step 1'),
- content: const Text('A')
- ),
- const Step(
- title: const Text('Step 2'),
- content: const Text('B')
- )
- ]
- )
- )
+ new MaterialApp(
+ home: new Material(
+ child: new Stepper(
+ type: StepperType.horizontal,
+ steps: <Step>[
+ const Step(
+ title: const Text('Step 1'),
+ content: const Text('A'),
+ ),
+ const Step(
+ title: const Text('Step 2'),
+ content: const Text('B'),
+ ),
+ ],
+ ),
+ ),
+ ),
);
expect(find.text('A'), findsOneWidget);
expect(find.text('B'), findsNothing);
await tester.pumpWidget(
- new Material(
- child: new Stepper(
- currentStep: 1,
- type: StepperType.horizontal,
- steps: <Step>[
- const Step(
- title: const Text('Step 1'),
- content: const Text('A')
- ),
- const Step(
- title: const Text('Step 2'),
- content: const Text('B')
- )
- ]
- )
- )
+ new MaterialApp(
+ home: new Material(
+ child: new Stepper(
+ currentStep: 1,
+ type: StepperType.horizontal,
+ steps: <Step>[
+ const Step(
+ title: const Text('Step 1'),
+ content: const Text('A'),
+ ),
+ const Step(
+ title: const Text('Step 2'),
+ content: const Text('B'),
+ ),
+ ],
+ ),
+ ),
+ ),
);
expect(find.text('A'), findsNothing);
@@ -175,33 +187,35 @@
bool cancelPressed = false;
await tester.pumpWidget(
- new Material(
- child: new Stepper(
- type: StepperType.horizontal,
- onStepContinue: () {
- continuePressed = true;
- },
- onStepCancel: () {
- cancelPressed = true;
- },
- steps: <Step>[
- const Step(
- title: const Text('Step 1'),
- content: const SizedBox(
- width: 100.0,
- height: 100.0
- )
- ),
- const Step(
- title: const Text('Step 2'),
- content: const SizedBox(
- width: 200.0,
- height: 200.0
- )
- )
- ]
- )
- )
+ new MaterialApp(
+ home: new Material(
+ child: new Stepper(
+ type: StepperType.horizontal,
+ onStepContinue: () {
+ continuePressed = true;
+ },
+ onStepCancel: () {
+ cancelPressed = true;
+ },
+ steps: <Step>[
+ const Step(
+ title: const Text('Step 1'),
+ content: const SizedBox(
+ width: 100.0,
+ height: 100.0,
+ ),
+ ),
+ const Step(
+ title: const Text('Step 2'),
+ content: const SizedBox(
+ width: 200.0,
+ height: 200.0,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
);
await tester.tap(find.text('CONTINUE'));
@@ -215,30 +229,32 @@
int index = 0;
await tester.pumpWidget(
- new Material(
- child: new Stepper(
- onStepTapped: (int i) {
- index = i;
- },
- steps: <Step>[
- const Step(
- title: const Text('Step 1'),
- content: const SizedBox(
- width: 100.0,
- height: 100.0
- )
- ),
- const Step(
- title: const Text('Step 2'),
- state: StepState.disabled,
- content: const SizedBox(
- width: 100.0,
- height: 100.0
- )
- )
- ]
- )
- )
+ new MaterialApp(
+ home: new Material(
+ child: new Stepper(
+ onStepTapped: (int i) {
+ index = i;
+ },
+ steps: <Step>[
+ const Step(
+ title: const Text('Step 1'),
+ content: const SizedBox(
+ width: 100.0,
+ height: 100.0,
+ ),
+ ),
+ const Step(
+ title: const Text('Step 2'),
+ state: StepState.disabled,
+ content: const SizedBox(
+ width: 100.0,
+ height: 100.0,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
);
await tester.tap(find.text('Step 2'));
@@ -247,33 +263,35 @@
testWidgets('Stepper scroll test', (WidgetTester tester) async {
await tester.pumpWidget(
- new Material(
- child: new Stepper(
- steps: <Step>[
- const Step(
- title: const Text('Step 1'),
- content: const SizedBox(
- width: 100.0,
- height: 300.0
- )
- ),
- const Step(
- title: const Text('Step 2'),
- content: const SizedBox(
- width: 100.0,
- height: 300.0
- )
- ),
- const Step(
- title: const Text('Step 3'),
- content: const SizedBox(
- width: 100.0,
- height: 100.0
- )
- ),
- ]
- )
- )
+ new MaterialApp(
+ home: new Material(
+ child: new Stepper(
+ steps: <Step>[
+ const Step(
+ title: const Text('Step 1'),
+ content: const SizedBox(
+ width: 100.0,
+ height: 300.0,
+ ),
+ ),
+ const Step(
+ title: const Text('Step 2'),
+ content: const SizedBox(
+ width: 100.0,
+ height: 300.0,
+ ),
+ ),
+ const Step(
+ title: const Text('Step 3'),
+ content: const SizedBox(
+ width: 100.0,
+ height: 100.0,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
);
final ScrollableState scrollableState = tester.firstState(find.byType(Scrollable));
@@ -281,34 +299,36 @@
await tester.tap(find.text('Step 3'));
await tester.pumpWidget(
- new Material(
- child: new Stepper(
- currentStep: 2,
- steps: <Step>[
- const Step(
- title: const Text('Step 1'),
- content: const SizedBox(
- width: 100.0,
- height: 300.0
- )
- ),
- const Step(
- title: const Text('Step 2'),
- content: const SizedBox(
- width: 100.0,
- height: 300.0
- )
- ),
- const Step(
- title: const Text('Step 3'),
- content: const SizedBox(
- width: 100.0,
- height: 100.0
- )
- ),
- ]
- )
- )
+ new MaterialApp(
+ home: new Material(
+ child: new Stepper(
+ currentStep: 2,
+ steps: <Step>[
+ const Step(
+ title: const Text('Step 1'),
+ content: const SizedBox(
+ width: 100.0,
+ height: 300.0,
+ ),
+ ),
+ const Step(
+ title: const Text('Step 2'),
+ content: const SizedBox(
+ width: 100.0,
+ height: 300.0,
+ ),
+ ),
+ const Step(
+ title: const Text('Step 3'),
+ content: const SizedBox(
+ width: 100.0,
+ height: 100.0,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
);
await tester.pump(const Duration(milliseconds: 100));
@@ -317,29 +337,31 @@
testWidgets('Stepper index test', (WidgetTester tester) async {
await tester.pumpWidget(
- new Center(
- child: new Material(
- child: new Stepper(
- steps: <Step>[
- const Step(
- title: const Text('A'),
- state: StepState.complete,
- content: const SizedBox(
- width: 100.0,
- height: 100.0
- )
- ),
- const Step(
- title: const Text('B'),
- content: const SizedBox(
- width: 100.0,
- height: 100.0
- )
- )
- ]
- )
- )
- )
+ new MaterialApp(
+ home: new Center(
+ child: new Material(
+ child: new Stepper(
+ steps: <Step>[
+ const Step(
+ title: const Text('A'),
+ state: StepState.complete,
+ content: const SizedBox(
+ width: 100.0,
+ height: 100.0,
+ ),
+ ),
+ const Step(
+ title: const Text('B'),
+ content: const SizedBox(
+ width: 100.0,
+ height: 100.0,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
);
expect(find.text('1'), findsNothing);
@@ -348,22 +370,24 @@
testWidgets('Stepper error test', (WidgetTester tester) async {
await tester.pumpWidget(
- new Center(
- child: new Material(
- child: new Stepper(
- steps: <Step>[
- const Step(
- title: const Text('A'),
- state: StepState.error,
- content: const SizedBox(
- width: 100.0,
- height: 100.0
- )
- )
- ]
- )
- )
- )
+ new MaterialApp(
+ home: new Center(
+ child: new Material(
+ child: new Stepper(
+ steps: <Step>[
+ const Step(
+ title: const Text('A'),
+ state: StepState.error,
+ content: const SizedBox(
+ width: 100.0,
+ height: 100.0,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
);
expect(find.text('!'), findsOneWidget);
diff --git a/packages/flutter/test/material/tabs_test.dart b/packages/flutter/test/material/tabs_test.dart
index b57b802..eb9a3b4 100644
--- a/packages/flutter/test/material/tabs_test.dart
+++ b/packages/flutter/test/material/tabs_test.dart
@@ -14,6 +14,15 @@
import '../rendering/recording_canvas.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 {
const StateMarker({ Key key, this.child }) : super(key: key);
@@ -41,7 +50,7 @@
bool isScrollable: false,
Color indicatorColor,
}) {
- return new Material(
+ return boilerplate(
child: new DefaultTabController(
initialIndex: tabs.indexOf(value),
length: tabs.length,
@@ -253,7 +262,7 @@
String value = tabs[0];
Widget builder() {
- return new Material(
+ return boilerplate(
child: new DefaultTabController(
initialIndex: tabs.indexOf(value),
length: tabs.length,
@@ -633,7 +642,7 @@
Color secondColor;
await tester.pumpWidget(
- new Material(
+ boilerplate(
child: new TabBar(
controller: controller,
labelColor: Colors.green[500],
@@ -667,7 +676,7 @@
);
await tester.pumpWidget(
- new Material(
+ boilerplate(
child: new TabBarView(
controller: controller,
children: <Widget>[ const Text('First'), const Text('Second') ],
@@ -765,7 +774,7 @@
);
Widget buildFrame() {
- return new Material(
+ return boilerplate(
child: new TabBar(
key: new UniqueKey(),
controller: controller,
@@ -893,7 +902,7 @@
);
await tester.pumpWidget(
- new Material(
+ boilerplate(
child: new TabBar(
isScrollable: true,
controller: controller,
@@ -927,7 +936,7 @@
);
await tester.pumpWidget(
- new Material(
+ boilerplate(
child: new Column(
children: <Widget>[
new TabBar(
@@ -985,7 +994,7 @@
);
await tester.pumpWidget(
- new Material(
+ boilerplate(
child: new Semantics(
container: true,
child: new TabBar(
@@ -1034,7 +1043,7 @@
);
await tester.pumpWidget(
- new Material(
+ boilerplate(
child: new Column(
children: <Widget>[
new TabBar(
@@ -1074,7 +1083,7 @@
);
await tester.pumpWidget(
- new Material(
+ boilerplate(
child: new Column(
children: <Widget>[
new TabBar(
@@ -1129,7 +1138,7 @@
);
await tester.pumpWidget(
- new Material(
+ boilerplate(
child: new Column(
children: <Widget>[
new TabBar(
diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart
index 5c5f13e..f33fa21 100644
--- a/packages/flutter/test/material/text_field_test.dart
+++ b/packages/flutter/test/material/text_field_test.dart
@@ -27,15 +27,36 @@
}
}
-Widget overlay(Widget child) {
- return new MediaQuery(
- data: const MediaQueryData(size: const Size(800.0, 600.0)),
- child: new Overlay(
- initialEntries: <OverlayEntry>[
- new OverlayEntry(
- builder: (BuildContext context) => child,
+Widget overlay({ Widget child }) {
+ return new Directionality(
+ textDirection: TextDirection.ltr,
+ child: new MediaQuery(
+ data: const MediaQueryData(size: const Size(800.0, 600.0)),
+ child: new Overlay(
+ initialEntries: <OverlayEntry>[
+ new OverlayEntry(
+ builder: (BuildContext context) => new Center(
+ child: new Material(
+ child: child,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+}
+
+Widget boilerplate({ Widget child }) {
+ return new Directionality(
+ textDirection: TextDirection.ltr,
+ child: new MediaQuery(
+ data: const MediaQueryData(size: const Size(800.0, 600.0)),
+ child: new Center(
+ child: new Material(
+ child: child,
),
- ],
+ ),
),
);
}
@@ -100,23 +121,19 @@
final Key textFieldKey = new UniqueKey();
String textFieldValue;
- Widget builder() {
- return new Center(
- child: new Material(
- child: new TextField(
- key: textFieldKey,
- decoration: const InputDecoration(
- hintText: 'Placeholder',
- ),
- onChanged: (String value) {
- textFieldValue = value;
- }
+ await tester.pumpWidget(
+ overlay(
+ child: new TextField(
+ key: textFieldKey,
+ decoration: const InputDecoration(
+ hintText: 'Placeholder',
),
+ onChanged: (String value) {
+ textFieldValue = value;
+ }
),
- );
- }
-
- await tester.pumpWidget(builder());
+ )
+ );
RenderBox findTextFieldBox() => tester.renderObject(find.byKey(textFieldKey));
@@ -126,11 +143,8 @@
Future<Null> checkText(String testValue) async {
return TestAsyncUtils.guard(() async {
await tester.enterText(find.byType(TextField), testValue);
-
// Check that the onChanged event handler fired.
expect(textFieldValue, equals(testValue));
-
- await tester.pumpWidget(builder());
await skipPastScrollingAnimation(tester);
});
}
@@ -146,20 +160,15 @@
});
testWidgets('Cursor blinks', (WidgetTester tester) async {
-
- Widget builder() {
- return const Center(
- child: const Material(
- child: const TextField(
- decoration: const InputDecoration(
- hintText: 'Placeholder',
- ),
+ await tester.pumpWidget(
+ overlay(
+ child: const TextField(
+ decoration: const InputDecoration(
+ hintText: 'Placeholder',
),
),
- );
- }
-
- await tester.pumpWidget(builder());
+ ),
+ );
await tester.showKeyboard(find.byType(TextField));
final EditableTextState editableText = tester.state(find.byType(EditableText));
@@ -191,20 +200,16 @@
});
testWidgets('obscureText control test', (WidgetTester tester) async {
- Widget builder() {
- return const Center(
- child: const Material(
- child: const TextField(
- obscureText: true,
- decoration: const InputDecoration(
- hintText: 'Placeholder',
- ),
+ await tester.pumpWidget(
+ overlay(
+ child: const TextField(
+ obscureText: true,
+ decoration: const InputDecoration(
+ hintText: 'Placeholder',
),
),
- );
- }
-
- await tester.pumpWidget(builder());
+ ),
+ );
await tester.showKeyboard(find.byType(TextField));
const String testValue = 'ABC';
@@ -219,24 +224,18 @@
testWidgets('Caret position is updated on tap', (WidgetTester tester) async {
final TextEditingController controller = new TextEditingController();
- Widget builder() {
- return overlay(new Center(
- child: new Material(
- child: new TextField(
- controller: controller,
- ),
+ await tester.pumpWidget(
+ overlay(
+ child: new TextField(
+ controller: controller,
),
- ));
- }
-
- await tester.pumpWidget(builder());
+ )
+ );
expect(controller.selection.baseOffset, -1);
expect(controller.selection.extentOffset, -1);
final String testValue = 'abc def ghi';
await tester.enterText(find.byType(TextField), testValue);
-
- await tester.pumpWidget(builder());
await skipPastScrollingAnimation(tester);
// Tap to reposition the caret.
@@ -252,34 +251,17 @@
testWidgets('Can long press to select', (WidgetTester tester) async {
final TextEditingController controller = new TextEditingController();
- Widget builder() {
- return new MediaQuery(
- data: const MediaQueryData(),
- child: new Overlay(
- initialEntries: <OverlayEntry>[
- new OverlayEntry(
- builder: (BuildContext context) {
- return new Center(
- child: new Material(
- child: new TextField(
- controller: controller,
- ),
- ),
- );
- },
- ),
- ],
+ await tester.pumpWidget(
+ overlay(
+ child: new TextField(
+ controller: controller,
),
- );
- }
-
- await tester.pumpWidget(builder());
+ )
+ );
final String testValue = 'abc def ghi';
await tester.enterText(find.byType(TextField), testValue);
expect(controller.value.text, testValue);
-
- await tester.pumpWidget(builder());
await skipPastScrollingAnimation(tester);
expect(controller.selection.isCollapsed, true);
@@ -299,22 +281,16 @@
testWidgets('Can drag handles to change selection', (WidgetTester tester) async {
final TextEditingController controller = new TextEditingController();
- Widget builder() {
- return overlay(new Center(
- child: new Material(
- child: new TextField(
- controller: controller,
- ),
+ await tester.pumpWidget(
+ overlay(
+ child: new TextField(
+ controller: controller,
),
- ));
- }
-
- await tester.pumpWidget(builder());
+ ),
+ );
final String testValue = 'abc def ghi';
await tester.enterText(find.byType(TextField), testValue);
-
- await tester.pumpWidget(builder());
await skipPastScrollingAnimation(tester);
// Long press the 'e' to select 'def'.
@@ -344,7 +320,7 @@
await gesture.moveTo(newHandlePos);
await tester.pump();
await gesture.up();
- await tester.pumpWidget(builder());
+ await tester.pump();
expect(controller.selection.baseOffset, selection.baseOffset);
expect(controller.selection.extentOffset, selection.extentOffset+2);
@@ -357,7 +333,7 @@
await gesture.moveTo(newHandlePos);
await tester.pump();
await gesture.up();
- await tester.pumpWidget(builder());
+ await tester.pump();
expect(controller.selection.baseOffset, selection.baseOffset-2);
expect(controller.selection.extentOffset, selection.extentOffset+2);
@@ -366,26 +342,21 @@
testWidgets('Can use selection toolbar', (WidgetTester tester) async {
final TextEditingController controller = new TextEditingController();
- Widget builder() {
- return overlay(new Center(
- child: new Material(
- child: new TextField(
- controller: controller,
- ),
+ await tester.pumpWidget(
+ overlay(
+ child: new TextField(
+ controller: controller,
),
- ));
- }
-
- await tester.pumpWidget(builder());
+ ),
+ );
final String testValue = 'abc def ghi';
await tester.enterText(find.byType(TextField), testValue);
- await tester.pumpWidget(builder());
await skipPastScrollingAnimation(tester);
// Tap the selection handle to bring up the "paste / select all" menu.
await tester.tapAt(textOffsetToPosition(tester, testValue.indexOf('e')));
- await tester.pumpWidget(builder());
+ await tester.pump();
await tester.pump(const Duration(milliseconds: 200)); // skip past the frame where the opacity is zero
RenderEditable renderEditable = findRenderEditable(tester);
List<TextSelectionPoint> endpoints = globalize(
@@ -393,24 +364,23 @@
renderEditable,
);
await tester.tapAt(endpoints[0].point + const Offset(1.0, 1.0));
- await tester.pumpWidget(builder());
+ await tester.pump();
await tester.pump(const Duration(milliseconds: 200)); // skip past the frame where the opacity is zero
// SELECT ALL should select all the text.
await tester.tap(find.text('SELECT ALL'));
- await tester.pumpWidget(builder());
+ await tester.pump();
expect(controller.selection.baseOffset, 0);
expect(controller.selection.extentOffset, testValue.length);
// COPY should reset the selection.
await tester.tap(find.text('COPY'));
- await tester.pumpWidget(builder());
await skipPastScrollingAnimation(tester);
expect(controller.selection.isCollapsed, true);
// Tap again to bring back the menu.
await tester.tapAt(textOffsetToPosition(tester, testValue.indexOf('e')));
- await tester.pumpWidget(builder());
+ await tester.pump();
await tester.pump(const Duration(milliseconds: 200)); // skip past the frame where the opacity is zero
renderEditable = findRenderEditable(tester);
endpoints = globalize(
@@ -418,38 +388,33 @@
renderEditable,
);
await tester.tapAt(endpoints[0].point + const Offset(1.0, 1.0));
- await tester.pumpWidget(builder());
+ await tester.pump();
await tester.pump(const Duration(milliseconds: 200)); // skip past the frame where the opacity is zero
// PASTE right before the 'e'.
await tester.tap(find.text('PASTE'));
- await tester.pumpWidget(builder());
+ await tester.pump();
expect(controller.text, 'abc d${testValue}ef ghi');
});
testWidgets('Selection toolbar fades in', (WidgetTester tester) async {
final TextEditingController controller = new TextEditingController();
- Widget builder() {
- return overlay(new Center(
- child: new Material(
- child: new TextField(
- controller: controller,
- ),
+ await tester.pumpWidget(
+ overlay(
+ child: new TextField(
+ controller: controller,
),
- ));
- }
-
- await tester.pumpWidget(builder());
+ ),
+ );
final String testValue = 'abc def ghi';
await tester.enterText(find.byType(TextField), testValue);
- await tester.pumpWidget(builder());
await skipPastScrollingAnimation(tester);
// Tap the selection handle to bring up the "paste / select all" menu.
await tester.tapAt(textOffsetToPosition(tester, testValue.indexOf('e')));
- await tester.pumpWidget(builder());
+ await tester.pump();
await tester.pump(const Duration(milliseconds: 200)); // skip past the frame where the opacity is zero
final RenderEditable renderEditable = findRenderEditable(tester);
final List<TextSelectionPoint> endpoints = globalize(
@@ -457,7 +422,7 @@
renderEditable,
);
await tester.tapAt(endpoints[0].point + const Offset(1.0, 1.0));
- await tester.pumpWidget(builder());
+ await tester.pump();
// Toolbar should fade in. Starting at 0% opacity.
final Element target = tester.element(find.text('SELECT ALL'));
@@ -478,15 +443,13 @@
final Key textFieldKey = new UniqueKey();
Widget builder(int maxLines) {
- return new Center(
- child: new Material(
- child: new TextField(
- key: textFieldKey,
- style: const TextStyle(color: Colors.black, fontSize: 34.0),
- maxLines: maxLines,
- decoration: const InputDecoration(
- hintText: 'Placeholder',
- ),
+ return boilerplate(
+ child: new TextField(
+ key: textFieldKey,
+ style: const TextStyle(color: Colors.black, fontSize: 34.0),
+ maxLines: maxLines,
+ decoration: const InputDecoration(
+ hintText: 'Placeholder',
),
),
);
@@ -543,25 +506,19 @@
testWidgets('Can drag handles to change selection in multiline', (WidgetTester tester) async {
final TextEditingController controller = new TextEditingController();
- Widget builder() {
- return overlay(new Center(
- child: new Material(
- child: new TextField(
- controller: controller,
- style: const TextStyle(color: Colors.black, fontSize: 34.0),
- maxLines: 3,
- ),
+ await tester.pumpWidget(
+ overlay(
+ child: new TextField(
+ controller: controller,
+ style: const TextStyle(color: Colors.black, fontSize: 34.0),
+ maxLines: 3,
),
- ));
- }
-
- await tester.pumpWidget(builder());
+ ),
+ );
final String testValue = kThreeLines;
final String cutValue = 'First line of stuff ';
await tester.enterText(find.byType(TextField), testValue);
-
- await tester.pumpWidget(builder());
await skipPastScrollingAnimation(tester);
// Check that the text spans multiple lines.
@@ -599,7 +556,7 @@
await gesture.moveTo(newHandlePos);
await tester.pump();
await gesture.up();
- await tester.pumpWidget(builder());
+ await tester.pump();
expect(controller.selection.baseOffset, 39);
expect(controller.selection.extentOffset, 50);
@@ -612,13 +569,13 @@
await gesture.moveTo(newHandlePos);
await tester.pump();
await gesture.up();
- await tester.pumpWidget(builder());
+ await tester.pump();
expect(controller.selection.baseOffset, 5);
expect(controller.selection.extentOffset, 50);
await tester.tap(find.text('CUT'));
- await tester.pumpWidget(builder());
+ await tester.pump();
expect(controller.selection.isCollapsed, true);
expect(controller.text, cutValue);
});
@@ -627,25 +584,21 @@
final Key textFieldKey = new UniqueKey();
final TextEditingController controller = new TextEditingController();
- Widget builder() {
- return overlay(new Center(
- child: new Material(
- child: new TextField(
- key: textFieldKey,
- controller: controller,
- style: const TextStyle(color: Colors.black, fontSize: 34.0),
- maxLines: 2,
- ),
+ await tester.pumpWidget(
+ overlay(
+ child: new TextField(
+ key: textFieldKey,
+ controller: controller,
+ style: const TextStyle(color: Colors.black, fontSize: 34.0),
+ maxLines: 2,
),
- ));
- }
-
- await tester.pumpWidget(builder());
+ ),
+ );
await tester.pump(const Duration(seconds: 1));
await tester.enterText(find.byType(TextField), kMoreThanFourLines);
- await tester.pumpWidget(builder());
+ await tester.pump();
await tester.pump(const Duration(seconds: 1));
RenderBox findInputBox() => tester.renderObject(find.byKey(textFieldKey));
@@ -717,20 +670,16 @@
testWidgets('TextField smoke test', (WidgetTester tester) async {
String textFieldValue;
- Widget builder() {
- return new Center(
- child: new Material(
- child: new TextField(
- decoration: null,
- onChanged: (String value) {
- textFieldValue = value;
- },
- ),
+ await tester.pumpWidget(
+ overlay(
+ child: new TextField(
+ decoration: null,
+ onChanged: (String value) {
+ textFieldValue = value;
+ },
),
- );
- }
-
- await tester.pumpWidget(builder());
+ ),
+ );
Future<Null> checkText(String testValue) {
return TestAsyncUtils.guard(() async {
@@ -739,7 +688,7 @@
// Check that the onChanged event handler fired.
expect(textFieldValue, equals(testValue));
- await tester.pumpWidget(builder());
+ await tester.pump();
});
}
@@ -750,21 +699,17 @@
final GlobalKey textFieldKey = new GlobalKey(debugLabel: 'textFieldKey');
String textFieldValue;
- Widget builder() {
- return new Center(
- child: new Material(
- child: new TextField(
- key: textFieldKey,
- decoration: const InputDecoration(
- hintText: 'Placeholder',
- ),
- onChanged: (String value) { textFieldValue = value; },
+ await tester.pumpWidget(
+ overlay(
+ child: new TextField(
+ key: textFieldKey,
+ decoration: const InputDecoration(
+ hintText: 'Placeholder',
),
+ onChanged: (String value) { textFieldValue = value; },
),
- );
- }
-
- await tester.pumpWidget(builder());
+ ),
+ );
Future<Null> checkText(String testValue) async {
return TestAsyncUtils.guard(() async {
@@ -773,7 +718,7 @@
// Check that the onChanged event handler fired.
expect(textFieldValue, equals(testValue));
- await tester.pumpWidget(builder());
+ await tester.pump();
});
}
@@ -781,20 +726,16 @@
});
testWidgets('TextField errorText trumps helperText', (WidgetTester tester) async {
- Widget builder() {
- return const Center(
- child: const Material(
- child: const TextField(
- decoration: const InputDecoration(
- errorText: 'error text',
- helperText: 'helper text',
- ),
+ await tester.pumpWidget(
+ overlay(
+ child: const TextField(
+ decoration: const InputDecoration(
+ errorText: 'error text',
+ helperText: 'helper text',
),
),
- );
- }
-
- await tester.pumpWidget(builder());
+ ),
+ );
expect(find.text('helper text'), findsNothing);
expect(find.text('error text'), findsOneWidget);
});
@@ -804,22 +745,18 @@
hintColor: Colors.blue[500],
);
- Widget builder() {
- return new Center(
+ await tester.pumpWidget(
+ overlay(
child: new Theme(
data: themeData,
- child: const Material(
- child: const TextField(
- decoration: const InputDecoration(
- helperText: 'helper text',
- ),
+ child: const TextField(
+ decoration: const InputDecoration(
+ helperText: 'helper text',
),
),
),
- );
- }
-
- await tester.pumpWidget(builder());
+ ),
+ );
final Text helperText = tester.widget(find.text('helper text'));
expect(helperText.style.color, themeData.hintColor);
expect(helperText.style.fontSize, themeData.textTheme.caption.fontSize);
@@ -831,20 +768,16 @@
fontSize: 10.0,
);
- Widget builder() {
- return new Center(
- child: new Material(
- child: new TextField(
- decoration: new InputDecoration(
- helperText: 'helper text',
- helperStyle: style,
- ),
+ await tester.pumpWidget(
+ overlay(
+ child: new TextField(
+ decoration: new InputDecoration(
+ helperText: 'helper text',
+ helperStyle: style,
),
),
- );
- }
-
- await tester.pumpWidget(builder());
+ ),
+ );
final Text helperText = tester.widget(find.text('helper text'));
expect(helperText.style, style);
});
@@ -858,23 +791,19 @@
hintColor: Colors.blue[500],
);
- Widget builder() {
- return new Center(
+ await tester.pumpWidget(
+ overlay(
child: new Theme(
data: themeData,
- child: new Material(
- child: new TextField(
- decoration: const InputDecoration(
- hintText: 'Placeholder',
- ),
- style: style,
+ child: new TextField(
+ decoration: const InputDecoration(
+ hintText: 'Placeholder',
),
+ style: style,
),
),
- );
- }
-
- await tester.pumpWidget(builder());
+ ),
+ );
final Text hintText = tester.widget(find.text('Placeholder'));
expect(hintText.style.color, themeData.hintColor);
@@ -887,20 +816,16 @@
fontSize: 10.0,
);
- Widget builder() {
- return new Center(
- child: new Material(
- child: new TextField(
- decoration: new InputDecoration(
- hintText: 'Placeholder',
- hintStyle: hintStyle,
- ),
+ await tester.pumpWidget(
+ overlay(
+ child: new TextField(
+ decoration: new InputDecoration(
+ hintText: 'Placeholder',
+ hintStyle: hintStyle,
),
),
- );
- }
-
- await tester.pumpWidget(builder());
+ ),
+ );
final Text hintText = tester.widget(find.text('Placeholder'));
expect(hintText.style, hintStyle);
@@ -912,20 +837,16 @@
fontSize: 10.0,
);
- Widget builder() {
- return new Center(
- child: new Material(
- child: new TextField(
- decoration: new InputDecoration(
- prefixText: 'Prefix:',
- prefixStyle: prefixStyle,
- ),
+ await tester.pumpWidget(
+ overlay(
+ child: new TextField(
+ decoration: new InputDecoration(
+ prefixText: 'Prefix:',
+ prefixStyle: prefixStyle,
),
),
- );
- }
-
- await tester.pumpWidget(builder());
+ ),
+ );
final Text prefixText = tester.widget(find.text('Prefix:'));
expect(prefixText.style, prefixStyle);
@@ -937,20 +858,16 @@
fontSize: 10.0,
);
- Widget builder() {
- return new Center(
- child: new Material(
- child: new TextField(
- decoration: new InputDecoration(
- suffixText: '.com',
- suffixStyle: suffixStyle,
- ),
+ await tester.pumpWidget(
+ overlay(
+ child: new TextField(
+ decoration: new InputDecoration(
+ suffixText: '.com',
+ suffixStyle: suffixStyle,
),
),
- );
- }
-
- await tester.pumpWidget(builder());
+ ),
+ );
final Text suffixText = tester.widget(find.text('.com'));
expect(suffixText.style, suffixStyle);
@@ -960,31 +877,26 @@
(WidgetTester tester) async {
final Key secondKey = new UniqueKey();
- Widget innerBuilder() {
- return new Center(
- child: new Material(
- child: new Column(
- children: <Widget>[
- const TextField(
- decoration: const InputDecoration(
- labelText: 'First',
- ),
+ await tester.pumpWidget(
+ overlay(
+ child: new Column(
+ children: <Widget>[
+ const TextField(
+ decoration: const InputDecoration(
+ labelText: 'First',
),
- new TextField(
- key: secondKey,
- decoration: const InputDecoration(
- prefixText: 'Prefix',
- suffixText: 'Suffix',
- ),
+ ),
+ new TextField(
+ key: secondKey,
+ decoration: const InputDecoration(
+ prefixText: 'Prefix',
+ suffixText: 'Suffix',
),
- ],
- ),
+ ),
+ ],
),
- );
- }
- Widget builder() => overlay(innerBuilder());
-
- await tester.pumpWidget(builder());
+ ),
+ );
expect(find.text('Prefix'), findsOneWidget);
expect(find.text('Suffix'), findsOneWidget);
@@ -1013,33 +925,28 @@
);
final Key secondKey = new UniqueKey();
- Widget innerBuilder() {
- return new Center(
- child: new Material(
- child: new Column(
- children: <Widget>[
- const TextField(
- decoration: const InputDecoration(
- labelText: 'First',
- ),
+ await tester.pumpWidget(
+ overlay(
+ child: new Column(
+ children: <Widget>[
+ const TextField(
+ decoration: const InputDecoration(
+ labelText: 'First',
),
- new TextField(
- key: secondKey,
- decoration: new InputDecoration(
- hintText: 'Hint',
- hintStyle: hintStyle,
- prefixText: 'Prefix',
- suffixText: 'Suffix',
- ),
+ ),
+ new TextField(
+ key: secondKey,
+ decoration: new InputDecoration(
+ hintText: 'Hint',
+ hintStyle: hintStyle,
+ prefixText: 'Prefix',
+ suffixText: 'Suffix',
),
- ],
- ),
+ ),
+ ],
),
- );
- }
- Widget builder() => overlay(innerBuilder());
-
- await tester.pumpWidget(builder());
+ ),
+ );
// Neither the prefix or the suffix should initially be visible, only the hint.
expect(find.text('Prefix'), findsNothing);
@@ -1089,34 +996,29 @@
);
final Key secondKey = new UniqueKey();
- Widget innerBuilder() {
- return new Center(
- child: new Material(
- child: new Column(
- children: <Widget>[
- const TextField(
- decoration: const InputDecoration(
- labelText: 'First',
- ),
+ await tester.pumpWidget(
+ overlay(
+ child: new Column(
+ children: <Widget>[
+ const TextField(
+ decoration: const InputDecoration(
+ labelText: 'First',
),
- new TextField(
- key: secondKey,
- decoration: new InputDecoration(
- labelText: 'Label',
- prefixText: 'Prefix',
- prefixStyle: prefixStyle,
- suffixText: 'Suffix',
- suffixStyle: suffixStyle,
- ),
+ ),
+ new TextField(
+ key: secondKey,
+ decoration: new InputDecoration(
+ labelText: 'Label',
+ prefixText: 'Prefix',
+ prefixStyle: prefixStyle,
+ suffixText: 'Suffix',
+ suffixStyle: suffixStyle,
),
- ],
- ),
+ ),
+ ],
),
- );
- }
- Widget builder() => overlay(innerBuilder());
-
- await tester.pumpWidget(builder());
+ ),
+ );
// Not focused. The prefix should not display, but the label should.
expect(find.text('Prefix'), findsNothing);
@@ -1151,30 +1053,25 @@
testWidgets('TextField label text animates', (WidgetTester tester) async {
final Key secondKey = new UniqueKey();
- Widget innerBuilder() {
- return new Center(
- child: new Material(
- child: new Column(
- children: <Widget>[
- const TextField(
- decoration: const InputDecoration(
- labelText: 'First',
- ),
+ await tester.pumpWidget(
+ overlay(
+ child: new Column(
+ children: <Widget>[
+ const TextField(
+ decoration: const InputDecoration(
+ labelText: 'First',
),
- new TextField(
- key: secondKey,
- decoration: const InputDecoration(
- labelText: 'Second',
- ),
+ ),
+ new TextField(
+ key: secondKey,
+ decoration: const InputDecoration(
+ labelText: 'Second',
),
- ],
- ),
+ ),
+ ],
),
- );
- }
- Widget builder() => overlay(innerBuilder());
-
- await tester.pumpWidget(builder());
+ ),
+ );
Offset pos = tester.getTopLeft(find.text('Second'));
@@ -1196,13 +1093,11 @@
testWidgets('No space between Input icon and text', (WidgetTester tester) async {
await tester.pumpWidget(
- const Center(
- child: const Material(
- child: const TextField(
- decoration: const InputDecoration(
- icon: const Icon(Icons.phone),
- labelText: 'label',
- ),
+ overlay(
+ child: const TextField(
+ decoration: const InputDecoration(
+ icon: const Icon(Icons.phone),
+ labelText: 'label',
),
),
),
@@ -1215,15 +1110,13 @@
testWidgets('Collapsed hint text placement', (WidgetTester tester) async {
await tester.pumpWidget(
- overlay(const Center(
- child: const Material(
- child: const TextField(
- decoration: const InputDecoration.collapsed(
- hintText: 'hint',
- ),
+ overlay(
+ child: const TextField(
+ decoration: const InputDecoration.collapsed(
+ hintText: 'hint',
),
),
- )),
+ ),
);
expect(tester.getTopLeft(find.text('hint')), equals(tester.getTopLeft(find.byType(TextField))));
@@ -1231,17 +1124,15 @@
testWidgets('Can align to center', (WidgetTester tester) async {
await tester.pumpWidget(
- overlay(new Center(
- child: new Material(
- child: new Container(
- width: 300.0,
- child: const TextField(
- textAlign: TextAlign.center,
- decoration: null,
- ),
+ overlay(
+ child: new Container(
+ width: 300.0,
+ child: const TextField(
+ textAlign: TextAlign.center,
+ decoration: null,
),
),
- )),
+ ),
);
final RenderEditable editable = findRenderEditable(tester);
@@ -1263,19 +1154,17 @@
testWidgets('Can align to center within center', (WidgetTester tester) async {
await tester.pumpWidget(
- overlay(new Center(
- child: new Material(
- child: new Container(
- width: 300.0,
- child: const Center(
- child: const TextField(
- textAlign: TextAlign.center,
- decoration: null,
- ),
+ overlay(
+ child: new Container(
+ width: 300.0,
+ child: const Center(
+ child: const TextField(
+ textAlign: TextAlign.center,
+ decoration: null,
),
),
),
- )),
+ ),
);
final RenderEditable editable = findRenderEditable(tester);
@@ -1307,17 +1196,15 @@
StateSetter setState;
await tester.pumpWidget(
- overlay(new Center(
- child: new Material(
- child: new StatefulBuilder(
- builder: (BuildContext context, StateSetter setter) {
- setState = setter;
- return new TextField(controller: currentController);
- }
- ),
+ overlay(
+ child: new StatefulBuilder(
+ builder: (BuildContext context, StateSetter setter) {
+ setState = setter;
+ return new TextField(controller: currentController);
+ }
),
),
- ));
+ );
expect(tester.testTextInput.editingState['text'], isEmpty);
await tester.tap(find.byType(TextField));
@@ -1352,178 +1239,149 @@
expect(tester.testTextInput.editingState['text'], equals(''));
});
- testWidgets(
- 'Cannot enter new lines onto single line TextField',
- (WidgetTester tester) async {
- final TextEditingController textController = new TextEditingController();
+ testWidgets('Cannot enter new lines onto single line TextField', (WidgetTester tester) async {
+ final TextEditingController textController = new TextEditingController();
- await tester.pumpWidget(new Material(
- child: new TextField(controller: textController, decoration: null),
- ));
+ await tester.pumpWidget(new Material(
+ child: new TextField(controller: textController, decoration: null),
+ ));
- await tester.enterText(find.byType(TextField), 'abc\ndef');
+ await tester.enterText(find.byType(TextField), 'abc\ndef');
- expect(textController.text, 'abcdef');
- }
- );
+ expect(textController.text, 'abcdef');
+ });
- testWidgets(
- 'Injected formatters are chained',
- (WidgetTester tester) async {
- final TextEditingController textController = new TextEditingController();
+ testWidgets('Injected formatters are chained', (WidgetTester tester) async {
+ final TextEditingController textController = new TextEditingController();
- await tester.pumpWidget(new Material(
+ await tester.pumpWidget(new Material(
+ child: new TextField(
+ controller: textController,
+ decoration: null,
+ inputFormatters: <TextInputFormatter> [
+ new BlacklistingTextInputFormatter(
+ new RegExp(r'[a-z]'),
+ replacementString: '#',
+ ),
+ ],
+ ),
+ ));
+
+ await tester.enterText(find.byType(TextField), 'a一b二c三\ndå››e五få…');
+ // The default single line formatter replaces \n with empty string.
+ expect(textController.text, '#一#二#三#å››#五#å…');
+ });
+
+ testWidgets('Chained formatters are in sequence', (WidgetTester tester) async {
+ final TextEditingController textController = new TextEditingController();
+
+ await tester.pumpWidget(new Material(
+ child: new TextField(
+ controller: textController,
+ decoration: null,
+ maxLines: 2,
+ inputFormatters: <TextInputFormatter> [
+ new BlacklistingTextInputFormatter(
+ new RegExp(r'[a-z]'),
+ replacementString: '12\n',
+ ),
+ new WhitelistingTextInputFormatter(new RegExp(r'\n[0-9]')),
+ ],
+ ),
+ ));
+
+ await tester.enterText(find.byType(TextField), 'a1b2c3');
+ // The first formatter turns it into
+ // 12\n112\n212\n3
+ // The second formatter turns it into
+ // \n1\n2\n3
+ // Multiline is allowed since maxLine != 1.
+ expect(textController.text, '\n1\n2\n3');
+ });
+
+ testWidgets('Pasted values are formatted', (WidgetTester tester) async {
+ final TextEditingController textController = new TextEditingController();
+
+ await tester.pumpWidget(
+ overlay(
child: new TextField(
controller: textController,
decoration: null,
inputFormatters: <TextInputFormatter> [
- new BlacklistingTextInputFormatter(
- new RegExp(r'[a-z]'),
- replacementString: '#',
- ),
+ WhitelistingTextInputFormatter.digitsOnly,
],
),
- ));
+ ),
+ );
- await tester.enterText(find.byType(TextField), 'a一b二c三\ndå››e五få…');
- // The default single line formatter replaces \n with empty string.
- expect(textController.text, '#一#二#三#å››#五#å…');
- }
- );
+ await tester.enterText(find.byType(TextField), 'a1b\n2c3');
+ expect(textController.text, '123');
+ await skipPastScrollingAnimation(tester);
- testWidgets(
- 'Chained formatters are in sequence',
- (WidgetTester tester) async {
- final TextEditingController textController = new TextEditingController();
+ await tester.tapAt(textOffsetToPosition(tester, '123'.indexOf('2')));
+ await tester.pump();
+ await tester.pump(const Duration(milliseconds: 200)); // skip past the frame where the opacity is zero
+ final RenderEditable renderEditable = findRenderEditable(tester);
+ final List<TextSelectionPoint> endpoints = globalize(
+ renderEditable.getEndpointsForSelection(textController.selection),
+ renderEditable,
+ );
+ await tester.tapAt(endpoints[0].point + const Offset(1.0, 1.0));
+ await tester.pump();
+ await tester.pump(const Duration(milliseconds: 200)); // skip past the frame where the opacity is zero
- await tester.pumpWidget(new Material(
- child: new TextField(
- controller: textController,
- decoration: null,
- maxLines: 2,
- inputFormatters: <TextInputFormatter> [
- new BlacklistingTextInputFormatter(
- new RegExp(r'[a-z]'),
- replacementString: '12\n',
- ),
- new WhitelistingTextInputFormatter(new RegExp(r'\n[0-9]')),
- ],
+ Clipboard.setData(const ClipboardData(text: '一4二\n5三6'));
+ await tester.tap(find.text('PASTE'));
+ await tester.pump();
+ // Puts 456 before the 2 in 123.
+ expect(textController.text, '145623');
+ });
+
+ testWidgets('Text field scrolls the caret into view', (WidgetTester tester) async {
+ final TextEditingController controller = new TextEditingController();
+
+ await tester.pumpWidget(
+ overlay(
+ child: new Container(
+ width: 100.0,
+ child: new TextField(
+ controller: controller,
+ ),
),
- ));
+ ),
+ );
- await tester.enterText(find.byType(TextField), 'a1b2c3');
- // The first formatter turns it into
- // 12\n112\n212\n3
- // The second formatter turns it into
- // \n1\n2\n3
- // Multiline is allowed since maxLine != 1.
- expect(textController.text, '\n1\n2\n3');
- }
- );
+ final String longText = 'a' * 20;
+ await tester.enterText(find.byType(TextField), longText);
+ await skipPastScrollingAnimation(tester);
- testWidgets(
- 'Pasted values are formatted',
- (WidgetTester tester) async {
- final TextEditingController textController = new TextEditingController();
+ ScrollableState scrollableState = tester.firstState(find.byType(Scrollable));
+ expect(scrollableState.position.pixels, equals(0.0));
- Widget builder() {
- return overlay(new Center(
- child: new Material(
- child: new TextField(
- controller: textController,
- decoration: null,
- inputFormatters: <TextInputFormatter> [
- WhitelistingTextInputFormatter.digitsOnly,
- ],
- ),
- ),
- ));
- }
+ // Move the caret to the end of the text and check that the text field
+ // scrolls to make the caret visible.
+ controller.selection = new TextSelection.collapsed(offset: longText.length);
+ await tester.pump(); // TODO(ianh): Figure out why this extra pump is needed.
+ await skipPastScrollingAnimation(tester);
- await tester.pumpWidget(builder());
-
- await tester.enterText(find.byType(TextField), 'a1b\n2c3');
- expect(textController.text, '123');
- await tester.pumpWidget(builder());
- await skipPastScrollingAnimation(tester);
-
- await tester.tapAt(textOffsetToPosition(tester, '123'.indexOf('2')));
- await tester.pumpWidget(builder());
- await tester.pump(const Duration(milliseconds: 200)); // skip past the frame where the opacity is zero
- final RenderEditable renderEditable = findRenderEditable(tester);
- final List<TextSelectionPoint> endpoints = globalize(
- renderEditable.getEndpointsForSelection(textController.selection),
- renderEditable,
- );
- await tester.tapAt(endpoints[0].point + const Offset(1.0, 1.0));
- await tester.pumpWidget(builder());
- await tester.pump(const Duration(milliseconds: 200)); // skip past the frame where the opacity is zero
-
- Clipboard.setData(const ClipboardData(text: '一4二\n5三6'));
- await tester.tap(find.text('PASTE'));
- await tester.pumpWidget(builder());
- // Puts 456 before the 2 in 123.
- expect(textController.text, '145623');
- }
- );
-
- testWidgets(
- 'Text field scrolls the caret into view',
- (WidgetTester tester) async {
- final TextEditingController controller = new TextEditingController();
-
- Widget builder() {
- return overlay(new Center(
- child: new Material(
- child: new Container(
- width: 100.0,
- child: new TextField(
- controller: controller,
- ),
- ),
- ),
- ));
- }
-
- await tester.pumpWidget(builder());
-
- final String longText = 'a' * 20;
- await tester.enterText(find.byType(TextField), longText);
- await tester.pumpWidget(builder());
- await skipPastScrollingAnimation(tester);
-
- ScrollableState scrollableState = tester.firstState(find.byType(Scrollable));
- expect(scrollableState.position.pixels, equals(0.0));
-
- // Move the caret to the end of the text and check that the text field
- // scrolls to make the caret visible.
- controller.selection = new TextSelection.collapsed(offset: longText.length);
- await tester.pumpWidget(builder());
- await skipPastScrollingAnimation(tester);
-
- scrollableState = tester.firstState(find.byType(Scrollable));
- expect(scrollableState.position.pixels, isNot(equals(0.0)));
- }
- );
+ scrollableState = tester.firstState(find.byType(Scrollable));
+ expect(scrollableState.position.pixels, isNot(equals(0.0)));
+ });
testWidgets('haptic feedback', (WidgetTester tester) async {
final FeedbackTester feedback = new FeedbackTester();
final TextEditingController controller = new TextEditingController();
- Widget builder() {
- return overlay(new Center(
- child: new Material(
- child: new Container(
- width: 100.0,
- child: new TextField(
- controller: controller,
- ),
+ await tester.pumpWidget(
+ overlay(
+ child: new Container(
+ width: 100.0,
+ child: new TextField(
+ controller: controller,
),
),
- ));
- }
-
- await tester.pumpWidget(builder());
+ ),
+ );
await tester.tap(find.byType(TextField));
await tester.pumpAndSettle(const Duration(seconds: 1));
@@ -1538,58 +1396,49 @@
feedback.dispose();
});
- testWidgets(
- 'Text field drops selection when losing focus',
- (WidgetTester tester) async {
- final Key key1 = new UniqueKey();
- final TextEditingController controller1 = new TextEditingController();
- final Key key2 = new UniqueKey();
+ testWidgets('Text field drops selection when losing focus', (WidgetTester tester) async {
+ final Key key1 = new UniqueKey();
+ final TextEditingController controller1 = new TextEditingController();
+ final Key key2 = new UniqueKey();
- Widget builder() {
- return overlay(new Center(
- child: new Material(
- child: new Column(
- children: <Widget>[
- new TextField(
- key: key1,
- controller: controller1
- ),
- new TextField(key: key2),
- ],
+ await tester.pumpWidget(
+ overlay(
+ child: new Column(
+ children: <Widget>[
+ new TextField(
+ key: key1,
+ controller: controller1
),
- ),
- ));
- }
+ new TextField(key: key2),
+ ],
+ ),
+ ),
+ );
- await tester.pumpWidget(builder());
- await tester.tap(find.byKey(key1));
- await tester.enterText(find.byKey(key1), 'abcd');
- await tester.pump();
- controller1.selection = const TextSelection(baseOffset: 0, extentOffset: 3);
- await tester.pump();
- expect(controller1.selection, isNot(equals(TextRange.empty)));
+ await tester.tap(find.byKey(key1));
+ await tester.enterText(find.byKey(key1), 'abcd');
+ await tester.pump();
+ controller1.selection = const TextSelection(baseOffset: 0, extentOffset: 3);
+ await tester.pump();
+ expect(controller1.selection, isNot(equals(TextRange.empty)));
- await tester.tap(find.byKey(key2));
- await tester.pump();
- expect(controller1.selection, equals(TextRange.empty));
- }
- );
+ await tester.tap(find.byKey(key2));
+ await tester.pump();
+ expect(controller1.selection, equals(TextRange.empty));
+ });
- testWidgets(
- 'Selection is consistent with text length',
- (WidgetTester tester) async {
- final TextEditingController controller = new TextEditingController();
+ testWidgets('Selection is consistent with text length', (WidgetTester tester) async {
+ final TextEditingController controller = new TextEditingController();
- controller.text = 'abcde';
- controller.selection = const TextSelection.collapsed(offset: 5);
+ controller.text = 'abcde';
+ controller.selection = const TextSelection.collapsed(offset: 5);
- controller.text = '';
- expect(controller.selection.start, lessThanOrEqualTo(0));
- expect(controller.selection.end, lessThanOrEqualTo(0));
+ controller.text = '';
+ expect(controller.selection.start, lessThanOrEqualTo(0));
+ expect(controller.selection.end, lessThanOrEqualTo(0));
- expect(() {
- controller.selection = const TextSelection.collapsed(offset: 10);
- }, throwsFlutterError);
- }
- );
+ expect(() {
+ controller.selection = const TextSelection.collapsed(offset: 10);
+ }, throwsFlutterError);
+ });
}
diff --git a/packages/flutter/test/painting/box_painter_test.dart b/packages/flutter/test/painting/box_painter_test.dart
index 672f033..f1d901d 100644
--- a/packages/flutter/test/painting/box_painter_test.dart
+++ b/packages/flutter/test/painting/box_painter_test.dart
@@ -167,7 +167,7 @@
],
).toString(),
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)',
),
);
});
diff --git a/packages/flutter/test/painting/edge_insets_test.dart b/packages/flutter/test/painting/edge_insets_test.dart
index f84a995..76baaa3 100644
--- a/packages/flutter/test/painting/edge_insets_test.dart
+++ b/packages/flutter/test/painting/edge_insets_test.dart
@@ -50,4 +50,141 @@
expect(EdgeInsets.lerp(null, b, 0.25), equals(b * 0.25));
expect(EdgeInsets.lerp(a, null, 0.25), equals(a * 0.75));
});
+
+ test('EdgeInsets.resolve()', () {
+ expect(new EdgeInsetsDirectional.fromSTEB(10.0, 20.0, 30.0, 40.0).resolve(TextDirection.ltr), const EdgeInsets.fromLTRB(10.0, 20.0, 30.0, 40.0));
+ expect(new EdgeInsetsDirectional.fromSTEB(99.0, 98.0, 97.0, 96.0).resolve(TextDirection.rtl), const EdgeInsets.fromLTRB(97.0, 98.0, 99.0, 96.0));
+ expect(new EdgeInsetsDirectional.only(start: 963.25).resolve(TextDirection.ltr), const EdgeInsets.fromLTRB(963.25, 0.0, 0.0, 0.0));
+ expect(new EdgeInsetsDirectional.only(top: 963.25).resolve(TextDirection.ltr), const EdgeInsets.fromLTRB(0.0, 963.25, 0.0, 0.0));
+ expect(new EdgeInsetsDirectional.only(end: 963.25).resolve(TextDirection.ltr), const EdgeInsets.fromLTRB(0.0, 0.0, 963.25, 0.0));
+ expect(new EdgeInsetsDirectional.only(bottom: 963.25).resolve(TextDirection.ltr), const EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 963.25));
+ expect(new EdgeInsetsDirectional.only(start: 963.25).resolve(TextDirection.rtl), const EdgeInsets.fromLTRB(0.0, 0.0, 963.25, 0.0));
+ expect(new EdgeInsetsDirectional.only(top: 963.25).resolve(TextDirection.rtl), const EdgeInsets.fromLTRB(0.0, 963.25, 0.0, 0.0));
+ expect(new EdgeInsetsDirectional.only(end: 963.25).resolve(TextDirection.rtl), const EdgeInsets.fromLTRB(963.25, 0.0, 0.0, 0.0));
+ expect(new EdgeInsetsDirectional.only(bottom: 963.25).resolve(TextDirection.rtl), const EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 963.25));
+ expect(new EdgeInsetsDirectional.only(), new EdgeInsetsDirectional.only());
+ expect(new EdgeInsetsDirectional.only(top: 1.0).hashCode, isNot(new EdgeInsetsDirectional.only(bottom: 1.0)));
+ expect(new EdgeInsetsDirectional.fromSTEB(10.0, 20.0, 30.0, 40.0).resolve(TextDirection.ltr),
+ new EdgeInsetsDirectional.fromSTEB(30.0, 20.0, 10.0, 40.0).resolve(TextDirection.rtl));
+ expect(new EdgeInsetsDirectional.fromSTEB(10.0, 20.0, 30.0, 40.0).resolve(TextDirection.ltr),
+ isNot(new EdgeInsetsDirectional.fromSTEB(30.0, 20.0, 10.0, 40.0).resolve(TextDirection.ltr)));
+ expect(new EdgeInsetsDirectional.fromSTEB(10.0, 20.0, 30.0, 40.0).resolve(TextDirection.ltr),
+ isNot(new EdgeInsetsDirectional.fromSTEB(10.0, 20.0, 30.0, 40.0).resolve(TextDirection.rtl)));
+ });
+
+ test('EdgeInsets equality', () {
+ expect(new EdgeInsetsDirectional.only(top: 5.0, bottom: 7.0), new EdgeInsetsDirectional.only(top: 5.0, bottom: 7.0));
+ expect(new EdgeInsets.only(top: 5.0, bottom: 7.0), new EdgeInsetsDirectional.only(top: 5.0, bottom: 7.0));
+ expect(new EdgeInsetsDirectional.only(top: 5.0, bottom: 7.0), new EdgeInsets.only(top: 5.0, bottom: 7.0));
+ expect(new EdgeInsets.only(top: 5.0, bottom: 7.0), new EdgeInsets.only(top: 5.0, bottom: 7.0));
+ expect(new EdgeInsetsDirectional.only(start: 5.0), new EdgeInsetsDirectional.only(start: 5.0));
+ expect(new EdgeInsets.only(left: 5.0), isNot(new EdgeInsetsDirectional.only(start: 5.0)));
+ expect(new EdgeInsetsDirectional.only(start: 5.0), isNot(new EdgeInsets.only(left: 5.0)));
+ expect(new EdgeInsets.only(left: 5.0), new EdgeInsets.only(left: 5.0));
+ expect(new EdgeInsetsDirectional.only(end: 5.0), new EdgeInsetsDirectional.only(end: 5.0));
+ expect(new EdgeInsets.only(right: 5.0), isNot(new EdgeInsetsDirectional.only(end: 5.0)));
+ expect(new EdgeInsetsDirectional.only(end: 5.0), isNot(new EdgeInsets.only(right: 5.0)));
+ expect(new EdgeInsets.only(right: 5.0), new EdgeInsets.only(right: 5.0));
+ expect(new EdgeInsetsDirectional.only(end: 5.0).add(new EdgeInsets.only(right: 5.0)), new EdgeInsetsDirectional.only(end: 5.0).add(new EdgeInsets.only(right: 5.0)));
+ expect(new EdgeInsetsDirectional.only(end: 5.0).add(new EdgeInsets.only(right: 5.0)), isNot(new EdgeInsetsDirectional.only(end: 5.0).add(new EdgeInsets.only(left: 5.0))));
+ expect(new EdgeInsetsDirectional.only(top: 1.0).add(new EdgeInsets.only(top: 2.0)), new EdgeInsetsDirectional.only(top: 3.0).add(new EdgeInsets.only(top: 0.0)));
+ expect(new EdgeInsetsDirectional.only(top: 1.0).add(new EdgeInsets.only(top: 2.0)), new EdgeInsets.only(top: 3.0).add(new EdgeInsetsDirectional.only(top: 0.0)));
+ expect(new EdgeInsetsDirectional.only(top: 1.0).add(new EdgeInsets.only(top: 2.0)), new EdgeInsetsDirectional.only(top: 3.0));
+ expect(new EdgeInsetsDirectional.only(top: 1.0).add(new EdgeInsets.only(top: 2.0)), new EdgeInsets.only(top: 3.0));
+ });
+
+ test('EdgeInsetsGeometry.lerp(...)', () {
+ expect(EdgeInsetsGeometry.lerp(new EdgeInsetsDirectional.only(end: 10.0), null, 0.5), new EdgeInsetsDirectional.only(end: 5.0));
+ expect(EdgeInsetsGeometry.lerp(new EdgeInsetsDirectional.only(start: 10.0), null, 0.5), new EdgeInsetsDirectional.only(start: 5.0));
+ expect(EdgeInsetsGeometry.lerp(new EdgeInsetsDirectional.only(top: 10.0), null, 0.5), new EdgeInsetsDirectional.only(top: 5.0));
+ expect(EdgeInsetsGeometry.lerp(new EdgeInsetsDirectional.only(bottom: 10.0), null, 0.5), new EdgeInsetsDirectional.only(bottom: 5.0));
+ expect(EdgeInsetsGeometry.lerp(new EdgeInsetsDirectional.only(bottom: 10.0), EdgeInsetsDirectional.zero, 0.5), new EdgeInsetsDirectional.only(bottom: 5.0));
+ expect(EdgeInsetsGeometry.lerp(new EdgeInsetsDirectional.only(bottom: 10.0), EdgeInsets.zero, 0.5), new EdgeInsetsDirectional.only(bottom: 5.0));
+ expect(EdgeInsetsGeometry.lerp(new EdgeInsetsDirectional.only(start: 10.0), new EdgeInsets.only(left: 20.0), 0.5), new EdgeInsetsDirectional.only(start: 5.0).add(new EdgeInsets.only(left: 10.0)));
+ expect(EdgeInsetsGeometry.lerp(new EdgeInsetsDirectional.only(start: 0.0, bottom: 1.0), new EdgeInsetsDirectional.only(start: 1.0, bottom: 1.0).add(new EdgeInsets.only(right: 2.0, bottom: 0.0)), 0.5), new EdgeInsetsDirectional.only(start: 0.5).add(new EdgeInsets.only(right: 1.0, bottom: 1.0)));
+ expect(EdgeInsetsGeometry.lerp(new EdgeInsets.only(left: 0.0, bottom: 1.0), new EdgeInsetsDirectional.only(end: 1.0, bottom: 1.0).add(new EdgeInsets.only(right: 2.0, bottom: 0.0)), 0.5), new EdgeInsetsDirectional.only(start: 0.0, end: 0.5).add(new EdgeInsets.only(right: 1.0, bottom: 1.0)));
+ });
+
+ test('EdgeInsetsGeometry.lerp(normal, ...)', () {
+ final EdgeInsets a = const EdgeInsets.all(10.0);
+ final EdgeInsets b = const EdgeInsets.all(20.0);
+ expect(EdgeInsetsGeometry.lerp(a, b, 0.25), equals(a * 1.25));
+ expect(EdgeInsetsGeometry.lerp(a, b, 0.25), equals(b * 0.625));
+ expect(EdgeInsetsGeometry.lerp(a, b, 0.25), equals(a + const EdgeInsets.all(2.5)));
+ expect(EdgeInsetsGeometry.lerp(a, b, 0.25), equals(b - const EdgeInsets.all(7.5)));
+
+ expect(EdgeInsetsGeometry.lerp(null, null, 0.25), isNull);
+ expect(EdgeInsetsGeometry.lerp(null, b, 0.25), equals(b * 0.25));
+ expect(EdgeInsetsGeometry.lerp(a, null, 0.25), equals(a * 0.75));
+ });
+
+ test('EdgeInsetsGeometry.lerp(directional, ...)', () {
+ final EdgeInsetsDirectional a = const EdgeInsetsDirectional.only(start: 10.0, end: 10.0);
+ final EdgeInsetsDirectional b = const EdgeInsetsDirectional.only(start: 20.0, end: 20.0);
+ expect(EdgeInsetsGeometry.lerp(a, b, 0.25), equals(a * 1.25));
+ expect(EdgeInsetsGeometry.lerp(a, b, 0.25), equals(b * 0.625));
+ expect(EdgeInsetsGeometry.lerp(a, b, 0.25), equals(a + const EdgeInsetsDirectional.only(start: 2.5, end: 2.5)));
+ expect(EdgeInsetsGeometry.lerp(a, b, 0.25), equals(b - const EdgeInsetsDirectional.only(start: 7.5, end: 7.5)));
+
+ expect(EdgeInsetsGeometry.lerp(null, null, 0.25), isNull);
+ expect(EdgeInsetsGeometry.lerp(null, b, 0.25), equals(b * 0.25));
+ expect(EdgeInsetsGeometry.lerp(a, null, 0.25), equals(a * 0.75));
+ });
+
+ test('EdgeInsetsGeometry.lerp(mixed, ...)', () {
+ final EdgeInsetsGeometry a = const EdgeInsetsDirectional.only(start: 10.0, end: 10.0).add(const EdgeInsets.all(1.0));
+ final EdgeInsetsGeometry b = const EdgeInsetsDirectional.only(start: 20.0, end: 20.0).add(const EdgeInsets.all(2.0));
+ expect(EdgeInsetsGeometry.lerp(a, b, 0.25), equals(a * 1.25));
+ expect(EdgeInsetsGeometry.lerp(a, b, 0.25), equals(b * 0.625));
+
+ expect(EdgeInsetsGeometry.lerp(null, null, 0.25), isNull);
+ expect(EdgeInsetsGeometry.lerp(null, b, 0.25), equals(b * 0.25));
+ expect(EdgeInsetsGeometry.lerp(a, null, 0.25), equals(a * 0.75));
+ });
+
+ test('EdgeInsets operators', () {
+ const EdgeInsets a = const EdgeInsets.fromLTRB(1.0, 2.0, 3.0, 5.0);
+ expect(a * 2.0, const EdgeInsets.fromLTRB(2.0, 4.0, 6.0, 10.0));
+ expect(a / 2.0, const EdgeInsets.fromLTRB(0.5, 1.0, 1.5, 2.5));
+ expect(a % 2.0, const EdgeInsets.fromLTRB(1.0, 0.0, 1.0, 1.0));
+ expect(a ~/ 2.0, const EdgeInsets.fromLTRB(0.0, 1.0, 1.0, 2.0));
+ expect(a + a, a * 2.0);
+ expect(a - a, EdgeInsets.zero);
+ expect(a.add(a), a * 2.0);
+ expect(a.subtract(a), EdgeInsets.zero);
+ });
+
+ test('EdgeInsetsDirectional operators', () {
+ const EdgeInsetsDirectional a = const EdgeInsetsDirectional.fromSTEB(1.0, 2.0, 3.0, 5.0);
+ expect(a * 2.0, const EdgeInsetsDirectional.fromSTEB(2.0, 4.0, 6.0, 10.0));
+ expect(a / 2.0, const EdgeInsetsDirectional.fromSTEB(0.5, 1.0, 1.5, 2.5));
+ expect(a % 2.0, const EdgeInsetsDirectional.fromSTEB(1.0, 0.0, 1.0, 1.0));
+ expect(a ~/ 2.0, const EdgeInsetsDirectional.fromSTEB(0.0, 1.0, 1.0, 2.0));
+ expect(a + a, a * 2.0);
+ expect(a - a, EdgeInsetsDirectional.zero);
+ expect(a.add(a), a * 2.0);
+ expect(a.subtract(a), EdgeInsetsDirectional.zero);
+ });
+
+ test('EdgeInsetsGeometry operators', () {
+ final EdgeInsetsGeometry a = const EdgeInsetsDirectional.fromSTEB(1.0, 2.0, 3.0, 5.0).add(EdgeInsets.zero);
+ expect(a, isNot(new isInstanceOf<EdgeInsetsDirectional>()));
+ expect(a * 2.0, const EdgeInsetsDirectional.fromSTEB(2.0, 4.0, 6.0, 10.0));
+ expect(a / 2.0, const EdgeInsetsDirectional.fromSTEB(0.5, 1.0, 1.5, 2.5));
+ expect(a % 2.0, const EdgeInsetsDirectional.fromSTEB(1.0, 0.0, 1.0, 1.0));
+ expect(a ~/ 2.0, const EdgeInsetsDirectional.fromSTEB(0.0, 1.0, 1.0, 2.0));
+ expect(a.add(a), a * 2.0);
+ expect(a.subtract(a), EdgeInsetsDirectional.zero);
+ expect(a.subtract(a), EdgeInsets.zero);
+ });
+
+ test('EdgeInsetsGeometry toString', () {
+ expect(new EdgeInsets.only().toString(), 'EdgeInsets.zero');
+ expect(new EdgeInsets.only(top: 1.01, left: 1.01, right: 1.01, bottom: 1.01).toString(), 'EdgeInsets.all(1.0)');
+ expect(new EdgeInsetsDirectional.only().toString(), 'EdgeInsets.zero');
+ expect(new EdgeInsetsDirectional.only(start: 1.01, end: 1.01, top: 1.01, bottom: 1.01).toString(), 'EdgeInsetsDirectional(1.0, 1.0, 1.0, 1.0)');
+ expect((new EdgeInsetsDirectional.only(start: 4.0).add(new EdgeInsets.only(top: 3.0))).toString(), 'EdgeInsetsDirectional(4.0, 3.0, 0.0, 0.0)');
+ expect((new EdgeInsetsDirectional.only(top: 4.0).add(new EdgeInsets.only(right: 3.0))).toString(), 'EdgeInsets(0.0, 4.0, 3.0, 0.0)');
+ expect((new EdgeInsetsDirectional.only(start: 4.0).add(new EdgeInsets.only(left: 3.0))).toString(), 'EdgeInsets(3.0, 0.0, 0.0, 0.0) + EdgeInsetsDirectional(4.0, 0.0, 0.0, 0.0)');
+ });
}
diff --git a/packages/flutter/test/painting/fractional_offset_test.dart b/packages/flutter/test/painting/fractional_offset_test.dart
index c89d9a1..bfb8e81 100644
--- a/packages/flutter/test/painting/fractional_offset_test.dart
+++ b/packages/flutter/test/painting/fractional_offset_test.dart
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:ui' show lerpDouble;
+
import 'package:flutter/painting.dart';
import 'package:flutter_test/flutter_test.dart';
@@ -23,8 +25,8 @@
expect(FractionalOffset.lerp(a, b, 0.25), equals(const FractionalOffset(0.125, 0.0)));
expect(FractionalOffset.lerp(null, null, 0.25), isNull);
- expect(FractionalOffset.lerp(null, b, 0.25), equals(b * 0.25));
- expect(FractionalOffset.lerp(a, null, 0.25), equals(a * 0.75));
+ expect(FractionalOffset.lerp(null, b, 0.25), equals(new FractionalOffset(0.5, 0.5 - 0.125)));
+ expect(FractionalOffset.lerp(a, null, 0.25), equals(new FractionalOffset(0.125, 0.125)));
});
test('FractionalOffset.fromOffsetAndSize()', () {
@@ -36,4 +38,107 @@
final FractionalOffset a = new FractionalOffset.fromOffsetAndRect(const Offset(150.0, 120.0), new Rect.fromLTWH(50.0, 20.0, 200.0, 400.0));
expect(a, const FractionalOffset(0.5, 0.25));
});
+
+ test('FractionalOffsetGeometry.resolve()', () {
+ expect(new FractionalOffsetDirectional(0.25, 0.3).resolve(TextDirection.ltr), const FractionalOffset(0.25, 0.3));
+ expect(new FractionalOffsetDirectional(0.25, 0.3).resolve(TextDirection.rtl), const FractionalOffset(0.75, 0.3));
+ expect(new FractionalOffsetDirectional(-0.25, 0.3).resolve(TextDirection.ltr), const FractionalOffset(-0.25, 0.3));
+ expect(new FractionalOffsetDirectional(-0.25, 0.3).resolve(TextDirection.rtl), const FractionalOffset(1.25, 0.3));
+ expect(new FractionalOffsetDirectional(1.25, 0.3).resolve(TextDirection.ltr), const FractionalOffset(1.25, 0.3));
+ expect(new FractionalOffsetDirectional(1.25, 0.3).resolve(TextDirection.rtl), const FractionalOffset(-0.25, 0.3));
+ expect(new FractionalOffsetDirectional(0.5, -0.3).resolve(TextDirection.ltr), const FractionalOffset(0.5, -0.3));
+ expect(new FractionalOffsetDirectional(0.5, -0.3).resolve(TextDirection.rtl), const FractionalOffset(0.5, -0.3));
+ expect(new FractionalOffsetDirectional(0.0, 0.0).resolve(TextDirection.ltr), const FractionalOffset(0.0, 0.0));
+ expect(new FractionalOffsetDirectional(0.0, 0.0).resolve(TextDirection.rtl), const FractionalOffset(1.0, 0.0));
+ expect(new FractionalOffsetDirectional(1.0, 1.0).resolve(TextDirection.ltr), const FractionalOffset(1.0, 1.0));
+ expect(new FractionalOffsetDirectional(1.0, 1.0).resolve(TextDirection.rtl), const FractionalOffset(0.0, 1.0));
+ expect(new FractionalOffsetDirectional(1.0, 2.0), new FractionalOffsetDirectional(1.0, 2.0));
+ expect(new FractionalOffsetDirectional(1.0, 2.0).hashCode, isNot(new FractionalOffsetDirectional(2.0, 1.0)));
+ expect(new FractionalOffsetDirectional(0.0, 0.0).resolve(TextDirection.ltr),
+ new FractionalOffsetDirectional(1.0, 0.0).resolve(TextDirection.rtl));
+ expect(new FractionalOffsetDirectional(0.0, 0.0).resolve(TextDirection.ltr),
+ isNot(new FractionalOffsetDirectional(1.0, 0.0).resolve(TextDirection.ltr)));
+ expect(new FractionalOffsetDirectional(1.0, 0.0).resolve(TextDirection.ltr),
+ isNot(new FractionalOffsetDirectional(1.0, 0.0).resolve(TextDirection.rtl)));
+ });
+
+ test('FractionalOffsetGeometry.lerp', () {
+ final FractionalOffsetGeometry directional1 = const FractionalOffsetDirectional(0.125, 0.625);
+ final FractionalOffsetGeometry normal1 = const FractionalOffset(0.25, 0.875);
+ final FractionalOffsetGeometry mixed1 = const FractionalOffset(0.0625, 0.5625).add(const FractionalOffsetDirectional(0.1875, 0.6875));
+ final FractionalOffsetGeometry directional2 = const FractionalOffsetDirectional(2.0, 3.0);
+ final FractionalOffsetGeometry normal2 = const FractionalOffset(2.0, 3.0);
+ final FractionalOffsetGeometry mixed2 = const FractionalOffset(2.0, 3.0).add(const FractionalOffsetDirectional(5.0, 3.0));
+
+ expect(FractionalOffsetGeometry.lerp(directional1, directional2, 0.5), const FractionalOffsetDirectional(0.125 + (2.0 - 0.125) / 2.0, 0.625 + (3.0 - 0.625) / 2.0));
+ expect(FractionalOffsetGeometry.lerp(directional2, directional2, 0.5), directional2);
+ expect(FractionalOffsetGeometry.lerp(directional1, normal2, 0.5).resolve(TextDirection.ltr), const FractionalOffset(1.0 + 1.0 / 16.0, 0.625 + (3.0 - 0.625) / 2.0));
+ expect(FractionalOffsetGeometry.lerp(directional1, normal2, 0.5).resolve(TextDirection.rtl), const FractionalOffset(1.0 + 15.0 / 16.0, 0.625 + (3.0 - 0.625) / 2.0));
+ expect(FractionalOffsetGeometry.lerp(directional1, mixed1, 0.5).resolve(TextDirection.ltr), new FractionalOffset(1.0 / 32.0 + 2.5 / 16.0, lerpDouble(0.625, 0.5625 + 0.6875, 0.5)));
+ expect(FractionalOffsetGeometry.lerp(directional1, mixed1, 0.5).resolve(TextDirection.rtl), new FractionalOffset(1.0 / 32.0 + 1.0 - 2.5 / 16.0, lerpDouble(0.625, 0.5625 + 0.6875, 0.5)));
+ expect(FractionalOffsetGeometry.lerp(mixed1, mixed2, 0.5).resolve(TextDirection.ltr), new FractionalOffset(3.0 + 5.0 / 8.0, lerpDouble(0.5625 + 0.6875, 6.0, 0.5)));
+ expect(FractionalOffsetGeometry.lerp(mixed1, mixed2, 0.5).resolve(TextDirection.rtl), new FractionalOffset(2.0 - 41.0 / 16.0, lerpDouble(0.5625 + 0.6875, 6.0, 0.5)));
+ expect(FractionalOffsetGeometry.lerp(normal1, normal2, 0.5), const FractionalOffset(0.25 + (2.0 - 0.25) / 2.0, 0.875 + (3.0 - 0.875) / 2.0));
+ expect(FractionalOffsetGeometry.lerp(normal1, mixed1, 0.5).resolve(TextDirection.ltr), new FractionalOffset(lerpDouble(0.25, 0.0625, 0.5) + lerpDouble(0.0, 0.1875, 0.5), lerpDouble(0.875, 0.5625 + 0.6875, 0.5)));
+ expect(FractionalOffsetGeometry.lerp(normal1, mixed1, 0.5).resolve(TextDirection.rtl), new FractionalOffset(lerpDouble(0.25, 0.0625, 0.5) + 1.0 - lerpDouble(0.0, 0.1875, 0.5), lerpDouble(0.875, 0.5625 + 0.6875, 0.5)));
+ expect(FractionalOffsetGeometry.lerp(null, mixed1, 0.5).resolve(TextDirection.ltr), FractionalOffsetGeometry.lerp(FractionalOffset.center, mixed1, 0.5).resolve(TextDirection.ltr));
+ expect(FractionalOffsetGeometry.lerp(mixed2, null, 0.25).resolve(TextDirection.ltr), FractionalOffsetGeometry.lerp(FractionalOffset.center, mixed2, 0.75).resolve(TextDirection.ltr));
+ expect(FractionalOffsetGeometry.lerp(directional1, null, 1.0), FractionalOffsetDirectional.center);
+ expect(FractionalOffsetGeometry.lerp(null, null, 0.5), isNull);
+ });
+
+ test('FractionalOffsetGeometry.lerp more', () {
+ final FractionalOffsetGeometry mixed1 = const FractionalOffset(10.0, 20.0).add(const FractionalOffsetDirectional(30.0, 50.0));
+ final FractionalOffsetGeometry mixed2 = const FractionalOffset(70.0, 110.0).add(const FractionalOffsetDirectional(130.0, 170.0));
+ final FractionalOffsetGeometry mixed3 = const FractionalOffset(25.0, 42.5).add(const FractionalOffsetDirectional(55.0, 80.0));
+
+ expect(FractionalOffsetGeometry.lerp(mixed1, mixed2, 0.0), mixed1);
+ expect(FractionalOffsetGeometry.lerp(mixed1, mixed2, 1.0), mixed2);
+ expect(FractionalOffsetGeometry.lerp(mixed1, mixed2, 0.25), mixed3);
+ });
+
+ test('FractionalOffsetGeometry add/subtract', () {
+ final FractionalOffsetGeometry directional = const FractionalOffsetDirectional(1.0, 2.0);
+ final FractionalOffsetGeometry normal = const FractionalOffset(3.0, 5.0);
+ expect(directional.add(normal).resolve(TextDirection.ltr), const FractionalOffset(4.0, 7.0));
+ expect(directional.add(normal).resolve(TextDirection.rtl), const FractionalOffset(3.0, 7.0));
+ expect(directional.subtract(normal).resolve(TextDirection.ltr), const FractionalOffset(-2.0, -3.0));
+ expect(directional.subtract(normal).resolve(TextDirection.rtl), const FractionalOffset(-3.0, -3.0));
+ expect(normal.add(normal), normal * 2.0);
+ expect(normal.subtract(normal), FractionalOffset.topLeft);
+ expect(directional.add(directional), directional * 2.0);
+ expect(directional.subtract(directional), FractionalOffsetDirectional.topStart);
+ });
+
+ test('FractionalOffsetGeometry operators', () {
+ expect(new FractionalOffsetDirectional(1.0, 2.0) * 2.0, new FractionalOffsetDirectional(2.0, 4.0));
+ expect(new FractionalOffsetDirectional(1.0, 2.0) / 2.0, new FractionalOffsetDirectional(0.5, 1.0));
+ expect(new FractionalOffsetDirectional(1.0, 2.0) % 2.0, new FractionalOffsetDirectional(1.0, 0.0));
+ expect(new FractionalOffsetDirectional(1.0, 2.0) ~/ 2.0, new FractionalOffsetDirectional(0.0, 1.0));
+ expect(FractionalOffset.topLeft.add(new FractionalOffsetDirectional(1.0, 2.0) * 2.0), new FractionalOffsetDirectional(2.0, 4.0));
+ expect(FractionalOffset.topLeft.add(new FractionalOffsetDirectional(1.0, 2.0) / 2.0), new FractionalOffsetDirectional(0.5, 1.0));
+ expect(FractionalOffset.topLeft.add(new FractionalOffsetDirectional(1.0, 2.0) % 2.0), new FractionalOffsetDirectional(1.0, 0.0));
+ expect(FractionalOffset.topLeft.add(new FractionalOffsetDirectional(1.0, 2.0) ~/ 2.0), new FractionalOffsetDirectional(0.0, 1.0));
+ expect(new FractionalOffset(1.0, 2.0) * 2.0, new FractionalOffset(2.0, 4.0));
+ expect(new FractionalOffset(1.0, 2.0) / 2.0, new FractionalOffset(0.5, 1.0));
+ expect(new FractionalOffset(1.0, 2.0) % 2.0, new FractionalOffset(1.0, 0.0));
+ expect(new FractionalOffset(1.0, 2.0) ~/ 2.0, new FractionalOffset(0.0, 1.0));
+ });
+
+ test('FractionalOffsetGeometry operators', () {
+ expect(new FractionalOffset(1.0, 2.0) + new FractionalOffset(3.0, 5.0), new FractionalOffset(4.0, 7.0));
+ expect(new FractionalOffset(1.0, 2.0) - new FractionalOffset(3.0, 5.0), new FractionalOffset(-2.0, -3.0));
+ expect(new FractionalOffsetDirectional(1.0, 2.0) + new FractionalOffsetDirectional(3.0, 5.0), new FractionalOffsetDirectional(4.0, 7.0));
+ expect(new FractionalOffsetDirectional(1.0, 2.0) - new FractionalOffsetDirectional(3.0, 5.0), new FractionalOffsetDirectional(-2.0, -3.0));
+ });
+
+ test('FractionalOffsetGeometry toString', () {
+ expect(new FractionalOffset(1.0001, 2.0001).toString(), 'FractionalOffset(1.0, 2.0)');
+ expect(new FractionalOffset(0.0, 0.0).toString(), 'FractionalOffset.topLeft');
+ expect(new FractionalOffset(0.0, 1.0).add(new FractionalOffsetDirectional(1.0, 0.0)).toString(), 'FractionalOffsetDirectional.bottomEnd');
+ expect(new FractionalOffset(0.0001, 0.0001).toString(), 'FractionalOffset(0.0, 0.0)');
+ expect(new FractionalOffset(0.0, 0.0).toString(), 'FractionalOffset.topLeft');
+ expect(new FractionalOffsetDirectional(0.0, 0.0).toString(), 'FractionalOffsetDirectional.topStart');
+ expect(new FractionalOffset(1.0, 1.0).add(new FractionalOffsetDirectional(1.0, 1.0)).toString(), 'FractionalOffset(1.0, 2.0) + FractionalOffsetDirectional(1.0, 0.0)');
+ });
}
diff --git a/packages/flutter/test/rendering/flex_test.dart b/packages/flutter/test/rendering/flex_test.dart
index 7583631..87ab3ff 100644
--- a/packages/flutter/test/rendering/flex_test.dart
+++ b/packages/flutter/test/rendering/flex_test.dart
@@ -11,13 +11,13 @@
void main() {
test('Overconstrained flex', () {
final RenderDecoratedBox box = new RenderDecoratedBox(decoration: const BoxDecoration());
- final RenderFlex flex = new RenderFlex(children: <RenderBox>[box]);
+ final RenderFlex flex = new RenderFlex(textDirection: TextDirection.ltr, children: <RenderBox>[box]);
layout(flex, constraints: const BoxConstraints(
minWidth: 200.0, maxWidth: 200.0, minHeight: 200.0, maxHeight: 200.0)
);
- expect(flex.size.width, equals(200.0), reason: "flex width");
- expect(flex.size.height, equals(200.0), reason: "flex height");
+ expect(flex.size.width, equals(200.0), reason: 'flex width');
+ expect(flex.size.height, equals(200.0), reason: 'flex height');
});
test('Vertical Overflow', () {
@@ -26,6 +26,7 @@
);
final RenderFlex flex = new RenderFlex(
direction: Axis.vertical,
+ verticalDirection: VerticalDirection.down,
children: <RenderBox>[
new RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(height: 200.0)),
flexible,
@@ -48,6 +49,7 @@
);
final RenderFlex flex = new RenderFlex(
direction: Axis.horizontal,
+ textDirection: TextDirection.ltr,
children: <RenderBox>[
new RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 200.0)),
flexible,
@@ -67,6 +69,7 @@
test('Vertical Flipped Constraints', () {
final RenderFlex flex = new RenderFlex(
direction: Axis.vertical,
+ verticalDirection: VerticalDirection.down,
children: <RenderBox>[
new RenderAspectRatio(aspectRatio: 1.0),
]
@@ -95,7 +98,7 @@
' mainAxisAlignment: start\n'
' mainAxisSize: max\n'
' crossAxisAlignment: center\n'
- ' textBaseline: null\n'
+ ' verticalDirection: down\n'
),
);
});
@@ -103,7 +106,7 @@
test('Parent data', () {
final RenderDecoratedBox box1 = new RenderDecoratedBox(decoration: const BoxDecoration());
final RenderDecoratedBox box2 = new RenderDecoratedBox(decoration: const BoxDecoration());
- final RenderFlex flex = new RenderFlex(children: <RenderBox>[box1, box2]);
+ final RenderFlex flex = new RenderFlex(textDirection: TextDirection.ltr, children: <RenderBox>[box1, box2]);
layout(flex, constraints: const BoxConstraints(
minWidth: 0.0, maxWidth: 100.0, minHeight: 0.0, maxHeight: 100.0)
);
@@ -125,7 +128,7 @@
test('Stretch', () {
final RenderDecoratedBox box1 = new RenderDecoratedBox(decoration: const BoxDecoration());
final RenderDecoratedBox box2 = new RenderDecoratedBox(decoration: const BoxDecoration());
- final RenderFlex flex = new RenderFlex();
+ final RenderFlex flex = new RenderFlex(textDirection: TextDirection.ltr);
flex.setupParentData(box2);
final FlexParentData box2ParentData = box2.parentData;
box2ParentData.flex = 2;
@@ -157,7 +160,7 @@
final RenderConstrainedBox box1 = new RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 100.0, height: 100.0));
final RenderConstrainedBox box2 = new RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 100.0, height: 100.0));
final RenderConstrainedBox box3 = new RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 100.0, height: 100.0));
- final RenderFlex flex = new RenderFlex(mainAxisAlignment: MainAxisAlignment.spaceEvenly);
+ final RenderFlex flex = new RenderFlex(textDirection: TextDirection.ltr, mainAxisAlignment: MainAxisAlignment.spaceEvenly);
flex.addAll(<RenderBox>[box1, box2, box3]);
layout(flex, constraints: const BoxConstraints(
minWidth: 0.0, maxWidth: 500.0, minHeight: 0.0, maxHeight: 400.0)
@@ -187,7 +190,7 @@
final RenderConstrainedBox box1 = new RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 100.0, height: 100.0));
final RenderConstrainedBox box2 = new RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 100.0, height: 100.0));
final RenderConstrainedBox box3 = new RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 100.0, height: 100.0));
- final RenderFlex flex = new RenderFlex(mainAxisAlignment: MainAxisAlignment.spaceBetween);
+ final RenderFlex flex = new RenderFlex(textDirection: TextDirection.ltr, mainAxisAlignment: MainAxisAlignment.spaceBetween);
flex.addAll(<RenderBox>[box1, box2, box3]);
layout(flex, constraints: const BoxConstraints(
minWidth: 0.0, maxWidth: 500.0, minHeight: 0.0, maxHeight: 400.0)
@@ -236,8 +239,9 @@
final RenderConstrainedBox box2 = new RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 100.0, height: 100.0));
final RenderConstrainedBox box3 = new RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 100.0, height: 100.0));
final RenderFlex flex = new RenderFlex(
+ textDirection: TextDirection.ltr,
mainAxisSize: MainAxisSize.min,
- mainAxisAlignment: MainAxisAlignment.spaceBetween
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
);
flex.addAll(<RenderBox>[box1, box2, box3]);
layout(flex, constraints: const BoxConstraints(
@@ -293,6 +297,7 @@
final RenderConstrainedBox box2 = new RenderConstrainedBox(additionalConstraints: square);
final RenderConstrainedBox box3 = new RenderConstrainedBox(additionalConstraints: square);
final RenderFlex flex = new RenderFlex(
+ textDirection: TextDirection.ltr,
mainAxisSize: MainAxisSize.min,
);
final RenderConstrainedOverflowBox parent = new RenderConstrainedOverflowBox(
@@ -337,6 +342,7 @@
final RenderConstrainedBox box2 = new RenderConstrainedBox(additionalConstraints: square);
final RenderConstrainedBox box3 = new RenderConstrainedBox(additionalConstraints: square);
final RenderFlex flex = new RenderFlex(
+ textDirection: TextDirection.ltr,
mainAxisSize: MainAxisSize.min,
);
final RenderConstrainedOverflowBox parent = new RenderConstrainedOverflowBox(
@@ -365,6 +371,7 @@
final RenderConstrainedBox box2 = new RenderConstrainedBox(additionalConstraints: square);
final RenderConstrainedBox box3 = new RenderConstrainedBox(additionalConstraints: square);
final RenderFlex flex = new RenderFlex(
+ textDirection: TextDirection.ltr,
mainAxisSize: MainAxisSize.max,
);
final RenderConstrainedOverflowBox parent = new RenderConstrainedOverflowBox(
@@ -383,4 +390,154 @@
expect(exceptions, isNotEmpty);
expect(exceptions.first, new isInstanceOf<FlutterError>());
});
+
+ test('Flex RTL', () {
+ final BoxConstraints square = const BoxConstraints.tightFor(width: 100.0, height: 100.0);
+ final RenderConstrainedBox box1 = new RenderConstrainedBox(additionalConstraints: square);
+ final RenderConstrainedBox box2 = new RenderConstrainedBox(additionalConstraints: square);
+ final RenderConstrainedBox box3 = new RenderConstrainedBox(additionalConstraints: square);
+ final RenderFlex flex = new RenderFlex(textDirection: TextDirection.ltr, children: <RenderBox>[box1, box2, box3]);
+ layout(flex);
+ expect(box1.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 250.0));
+ expect(box2.localToGlobal(const Offset(0.0, 0.0)), const Offset(100.0, 250.0));
+ expect(box3.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 250.0));
+ expect(box1.size, const Size(100.0, 100.0));
+ expect(box2.size, const Size(100.0, 100.0));
+ expect(box3.size, const Size(100.0, 100.0));
+
+ flex.mainAxisAlignment = MainAxisAlignment.end;
+ pumpFrame();
+ expect(box1.localToGlobal(const Offset(0.0, 0.0)), const Offset(500.0, 250.0));
+ expect(box2.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 250.0));
+ expect(box3.localToGlobal(const Offset(0.0, 0.0)), const Offset(700.0, 250.0));
+ expect(box1.size, const Size(100.0, 100.0));
+ expect(box2.size, const Size(100.0, 100.0));
+ expect(box3.size, const Size(100.0, 100.0));
+
+ flex.textDirection = TextDirection.rtl;
+ pumpFrame();
+ expect(box1.localToGlobal(const Offset(0.0, 0.0)), const Offset(200.0, 250.0));
+ expect(box2.localToGlobal(const Offset(0.0, 0.0)), const Offset(100.0, 250.0));
+ expect(box3.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 250.0));
+ expect(box1.size, const Size(100.0, 100.0));
+ expect(box2.size, const Size(100.0, 100.0));
+ expect(box3.size, const Size(100.0, 100.0));
+
+ flex.mainAxisAlignment = MainAxisAlignment.start;
+ pumpFrame();
+ expect(box1.localToGlobal(const Offset(0.0, 0.0)), const Offset(700.0, 250.0));
+ expect(box2.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 250.0));
+ expect(box3.localToGlobal(const Offset(0.0, 0.0)), const Offset(500.0, 250.0));
+ expect(box1.size, const Size(100.0, 100.0));
+ expect(box2.size, const Size(100.0, 100.0));
+ expect(box3.size, const Size(100.0, 100.0));
+
+ flex.crossAxisAlignment = CrossAxisAlignment.start; // vertical direction is down
+ pumpFrame();
+ expect(box1.localToGlobal(const Offset(0.0, 0.0)), const Offset(700.0, 0.0));
+ expect(box2.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0));
+ expect(box3.localToGlobal(const Offset(0.0, 0.0)), const Offset(500.0, 0.0));
+ expect(box1.size, const Size(100.0, 100.0));
+ expect(box2.size, const Size(100.0, 100.0));
+ expect(box3.size, const Size(100.0, 100.0));
+
+ flex.crossAxisAlignment = CrossAxisAlignment.end;
+ pumpFrame();
+ expect(box1.localToGlobal(const Offset(0.0, 0.0)), const Offset(700.0, 500.0));
+ expect(box2.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 500.0));
+ expect(box3.localToGlobal(const Offset(0.0, 0.0)), const Offset(500.0, 500.0));
+ expect(box1.size, const Size(100.0, 100.0));
+ expect(box2.size, const Size(100.0, 100.0));
+ expect(box3.size, const Size(100.0, 100.0));
+
+ flex.verticalDirection = VerticalDirection.up;
+ pumpFrame();
+ expect(box1.localToGlobal(const Offset(0.0, 0.0)), const Offset(700.0, 0.0));
+ expect(box2.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 0.0));
+ expect(box3.localToGlobal(const Offset(0.0, 0.0)), const Offset(500.0, 0.0));
+ expect(box1.size, const Size(100.0, 100.0));
+ expect(box2.size, const Size(100.0, 100.0));
+ expect(box3.size, const Size(100.0, 100.0));
+
+ flex.crossAxisAlignment = CrossAxisAlignment.start;
+ pumpFrame();
+ expect(box1.localToGlobal(const Offset(0.0, 0.0)), const Offset(700.0, 500.0));
+ expect(box2.localToGlobal(const Offset(0.0, 0.0)), const Offset(600.0, 500.0));
+ expect(box3.localToGlobal(const Offset(0.0, 0.0)), const Offset(500.0, 500.0));
+ expect(box1.size, const Size(100.0, 100.0));
+ expect(box2.size, const Size(100.0, 100.0));
+ expect(box3.size, const Size(100.0, 100.0));
+
+ flex.direction = Axis.vertical; // and main=start, cross=start, up, rtl
+ pumpFrame();
+ expect(box1.localToGlobal(const Offset(0.0, 0.0)), const Offset(700.0, 500.0));
+ expect(box2.localToGlobal(const Offset(0.0, 0.0)), const Offset(700.0, 400.0));
+ expect(box3.localToGlobal(const Offset(0.0, 0.0)), const Offset(700.0, 300.0));
+ expect(box1.size, const Size(100.0, 100.0));
+ expect(box2.size, const Size(100.0, 100.0));
+ expect(box3.size, const Size(100.0, 100.0));
+
+ flex.crossAxisAlignment = CrossAxisAlignment.end;
+ pumpFrame();
+ expect(box1.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 500.0));
+ expect(box2.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0));
+ expect(box3.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 300.0));
+ expect(box1.size, const Size(100.0, 100.0));
+ expect(box2.size, const Size(100.0, 100.0));
+ expect(box3.size, const Size(100.0, 100.0));
+
+ flex.crossAxisAlignment = CrossAxisAlignment.stretch;
+ pumpFrame();
+ expect(box1.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 500.0));
+ expect(box2.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0));
+ expect(box3.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 300.0));
+ expect(box1.size, const Size(800.0, 100.0));
+ expect(box2.size, const Size(800.0, 100.0));
+ expect(box3.size, const Size(800.0, 100.0));
+
+ flex.textDirection = TextDirection.ltr;
+ pumpFrame();
+ expect(box1.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 500.0));
+ expect(box2.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0));
+ expect(box3.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 300.0));
+ expect(box1.size, const Size(800.0, 100.0));
+ expect(box2.size, const Size(800.0, 100.0));
+ expect(box3.size, const Size(800.0, 100.0));
+
+ flex.crossAxisAlignment = CrossAxisAlignment.start;
+ pumpFrame();
+ expect(box1.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 500.0));
+ expect(box2.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 400.0));
+ expect(box3.localToGlobal(const Offset(0.0, 0.0)), const Offset(0.0, 300.0));
+ expect(box1.size, const Size(100.0, 100.0));
+ expect(box2.size, const Size(100.0, 100.0));
+ expect(box3.size, const Size(100.0, 100.0));
+
+ flex.crossAxisAlignment = CrossAxisAlignment.end;
+ pumpFrame();
+ expect(box1.localToGlobal(const Offset(0.0, 0.0)), const Offset(700.0, 500.0));
+ expect(box2.localToGlobal(const Offset(0.0, 0.0)), const Offset(700.0, 400.0));
+ expect(box3.localToGlobal(const Offset(0.0, 0.0)), const Offset(700.0, 300.0));
+ expect(box1.size, const Size(100.0, 100.0));
+ expect(box2.size, const Size(100.0, 100.0));
+ expect(box3.size, const Size(100.0, 100.0));
+
+ flex.verticalDirection = VerticalDirection.down;
+ pumpFrame();
+ expect(box1.localToGlobal(const Offset(0.0, 0.0)), const Offset(700.0, 0.0));
+ expect(box2.localToGlobal(const Offset(0.0, 0.0)), const Offset(700.0, 100.0));
+ expect(box3.localToGlobal(const Offset(0.0, 0.0)), const Offset(700.0, 200.0));
+ expect(box1.size, const Size(100.0, 100.0));
+ expect(box2.size, const Size(100.0, 100.0));
+ expect(box3.size, const Size(100.0, 100.0));
+
+ flex.mainAxisAlignment = MainAxisAlignment.end;
+ pumpFrame();
+ expect(box1.localToGlobal(const Offset(0.0, 0.0)), const Offset(700.0, 300.0));
+ expect(box2.localToGlobal(const Offset(0.0, 0.0)), const Offset(700.0, 400.0));
+ expect(box3.localToGlobal(const Offset(0.0, 0.0)), const Offset(700.0, 500.0));
+ expect(box1.size, const Size(100.0, 100.0));
+ expect(box2.size, const Size(100.0, 100.0));
+ expect(box3.size, const Size(100.0, 100.0));
+ });
}
diff --git a/packages/flutter/test/rendering/limited_box_test.dart b/packages/flutter/test/rendering/limited_box_test.dart
index 69a65c2..266f2e9 100644
--- a/packages/flutter/test/rendering/limited_box_test.dart
+++ b/packages/flutter/test/rendering/limited_box_test.dart
@@ -36,7 +36,7 @@
' │ parentData: <none>\n'
' │ constraints: BoxConstraints(w=800.0, h=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'
' │ maxWidth: Infinity\n'
' │ minHeight: 0.0\n'
@@ -122,7 +122,7 @@
' │ parentData: <none>\n'
' │ constraints: BoxConstraints(w=800.0, h=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'
' │ maxWidth: 500.0\n'
' │ minHeight: 0.0\n'
@@ -158,7 +158,7 @@
' │ parentData: <none>\n'
' │ constraints: BoxConstraints(w=800.0, h=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'
' │ maxWidth: use parent maxWidth constraint\n'
' │ minHeight: use parent minHeight constraint\n'
diff --git a/packages/flutter/test/rendering/mutations_test.dart b/packages/flutter/test/rendering/mutations_test.dart
index 431e379..4a1c09a 100644
--- a/packages/flutter/test/rendering/mutations_test.dart
+++ b/packages/flutter/test/rendering/mutations_test.dart
@@ -36,7 +36,7 @@
RenderBox child1, child2;
bool movedChild1 = 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(child2 = new RenderLayoutTestBox(() { movedChild2 = true; }));
diff --git a/packages/flutter/test/widgets/automatic_keep_alive_test.dart b/packages/flutter/test/widgets/automatic_keep_alive_test.dart
index 0f15b93..8b73f9f 100644
--- a/packages/flutter/test/widgets/automatic_keep_alive_test.dart
+++ b/packages/flutter/test/widgets/automatic_keep_alive_test.dart
@@ -196,32 +196,35 @@
group('Implied automatic keep-alive', () { tests(impliedMode: true); });
testWidgets('AutomaticKeepAlive double', (WidgetTester tester) async {
- await tester.pumpWidget(new ListView(
- addAutomaticKeepAlives: false,
- addRepaintBoundaries: false,
- children: <Widget>[
- new AutomaticKeepAlive(
- child: new Container(
- height: 400.0,
- child: new Row(children: <Widget>[
- new Leaf(key: const GlobalObjectKey<_LeafState>(0), child: const Placeholder()),
- new Leaf(key: const GlobalObjectKey<_LeafState>(1), child: const Placeholder()),
- ]),
+ await tester.pumpWidget(new Directionality(
+ textDirection: TextDirection.ltr,
+ child: new ListView(
+ addAutomaticKeepAlives: false,
+ addRepaintBoundaries: false,
+ children: <Widget>[
+ new AutomaticKeepAlive(
+ child: new Container(
+ height: 400.0,
+ child: new Row(children: <Widget>[
+ new Leaf(key: const GlobalObjectKey<_LeafState>(0), child: const Placeholder()),
+ new Leaf(key: const GlobalObjectKey<_LeafState>(1), child: const Placeholder()),
+ ]),
+ ),
),
- ),
- new AutomaticKeepAlive(
- child: new Container(
- key: const GlobalObjectKey<_LeafState>(2),
- height: 400.0,
+ new AutomaticKeepAlive(
+ child: new Container(
+ key: const GlobalObjectKey<_LeafState>(2),
+ height: 400.0,
+ ),
),
- ),
- new AutomaticKeepAlive(
- child: new Container(
- key: const GlobalObjectKey<_LeafState>(3),
- height: 400.0,
+ new AutomaticKeepAlive(
+ child: new Container(
+ key: const GlobalObjectKey<_LeafState>(3),
+ height: 400.0,
+ ),
),
- ),
- ],
+ ],
+ ),
));
expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget);
@@ -267,38 +270,41 @@
});
testWidgets('AutomaticKeepAlive double', (WidgetTester tester) async {
- await tester.pumpWidget(new ListView(
- addAutomaticKeepAlives: false,
- addRepaintBoundaries: false,
- children: <Widget>[
- new AutomaticKeepAlive(
- child: new Container(
- height: 400.0,
- child: new Row(children: <Widget>[
- new Leaf(key: const GlobalObjectKey<_LeafState>(0), child: const Placeholder()),
- new Leaf(key: const GlobalObjectKey<_LeafState>(1), child: const Placeholder()),
- ]),
+ await tester.pumpWidget(new Directionality(
+ textDirection: TextDirection.ltr,
+ child: new ListView(
+ addAutomaticKeepAlives: false,
+ addRepaintBoundaries: false,
+ children: <Widget>[
+ new AutomaticKeepAlive(
+ child: new Container(
+ height: 400.0,
+ child: new Row(children: <Widget>[
+ new Leaf(key: const GlobalObjectKey<_LeafState>(0), child: const Placeholder()),
+ new Leaf(key: const GlobalObjectKey<_LeafState>(1), child: const Placeholder()),
+ ]),
+ ),
),
- ),
- new AutomaticKeepAlive(
- child: new Container(
- height: 400.0,
- child: new Row(children: <Widget>[
- new Leaf(key: const GlobalObjectKey<_LeafState>(2), child: const Placeholder()),
- new Leaf(key: const GlobalObjectKey<_LeafState>(3), child: const Placeholder()),
- ]),
+ new AutomaticKeepAlive(
+ child: new Container(
+ height: 400.0,
+ child: new Row(children: <Widget>[
+ new Leaf(key: const GlobalObjectKey<_LeafState>(2), child: const Placeholder()),
+ new Leaf(key: const GlobalObjectKey<_LeafState>(3), child: const Placeholder()),
+ ]),
+ ),
),
- ),
- new AutomaticKeepAlive(
- child: new Container(
- height: 400.0,
- child: new Row(children: <Widget>[
- new Leaf(key: const GlobalObjectKey<_LeafState>(4), child: const Placeholder()),
- new Leaf(key: const GlobalObjectKey<_LeafState>(5), child: const Placeholder()),
- ]),
+ new AutomaticKeepAlive(
+ child: new Container(
+ height: 400.0,
+ child: new Row(children: <Widget>[
+ new Leaf(key: const GlobalObjectKey<_LeafState>(4), child: const Placeholder()),
+ new Leaf(key: const GlobalObjectKey<_LeafState>(5), child: const Placeholder()),
+ ]),
+ ),
),
- ),
- ],
+ ],
+ ),
));
expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget);
@@ -315,38 +321,41 @@
expect(find.byKey(const GlobalObjectKey<_LeafState>(3)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(4)), findsOneWidget);
expect(find.byKey(const GlobalObjectKey<_LeafState>(5)), findsOneWidget);
- await tester.pumpWidget(new ListView(
- addAutomaticKeepAlives: false,
- addRepaintBoundaries: false,
- children: <Widget>[
- new AutomaticKeepAlive(
- child: new Container(
- height: 400.0,
- child: new Row(children: <Widget>[
- new Leaf(key: const GlobalObjectKey<_LeafState>(1), child: const Placeholder()),
- ]),
+ await tester.pumpWidget(new Directionality(
+ textDirection: TextDirection.ltr,
+ child: new ListView(
+ addAutomaticKeepAlives: false,
+ addRepaintBoundaries: false,
+ children: <Widget>[
+ new AutomaticKeepAlive(
+ child: new Container(
+ height: 400.0,
+ child: new Row(children: <Widget>[
+ new Leaf(key: const GlobalObjectKey<_LeafState>(1), child: const Placeholder()),
+ ]),
+ ),
),
- ),
- new AutomaticKeepAlive(
- child: new Container(
- height: 400.0,
- child: new Row(children: <Widget>[
- new Leaf(key: const GlobalObjectKey<_LeafState>(2), child: const Placeholder()),
- new Leaf(key: const GlobalObjectKey<_LeafState>(3), child: const Placeholder()),
- ]),
+ new AutomaticKeepAlive(
+ child: new Container(
+ height: 400.0,
+ child: new Row(children: <Widget>[
+ new Leaf(key: const GlobalObjectKey<_LeafState>(2), child: const Placeholder()),
+ new Leaf(key: const GlobalObjectKey<_LeafState>(3), child: const Placeholder()),
+ ]),
+ ),
),
- ),
- new AutomaticKeepAlive(
- child: new Container(
- height: 400.0,
- child: new Row(children: <Widget>[
- new Leaf(key: const GlobalObjectKey<_LeafState>(4), child: const Placeholder()),
- new Leaf(key: const GlobalObjectKey<_LeafState>(5), child: const Placeholder()),
- new Leaf(key: const GlobalObjectKey<_LeafState>(0), child: const Placeholder()),
- ]),
+ new AutomaticKeepAlive(
+ child: new Container(
+ height: 400.0,
+ child: new Row(children: <Widget>[
+ new Leaf(key: const GlobalObjectKey<_LeafState>(4), child: const Placeholder()),
+ new Leaf(key: const GlobalObjectKey<_LeafState>(5), child: const Placeholder()),
+ new Leaf(key: const GlobalObjectKey<_LeafState>(0), child: const Placeholder()),
+ ]),
+ ),
),
- ),
- ],
+ ],
+ ),
));
await tester.pump(); // Sometimes AutomaticKeepAlive needs an extra pump to clean things up.
expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsNothing);
@@ -371,38 +380,41 @@
expect(find.byKey(const GlobalObjectKey<_LeafState>(4)), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(5)), findsNothing);
expect(find.byKey(const GlobalObjectKey<_LeafState>(0)), findsNothing);
- await tester.pumpWidget(new ListView(
- addAutomaticKeepAlives: false,
- addRepaintBoundaries: false,
- children: <Widget>[
- new AutomaticKeepAlive(
- child: new Container(
- height: 400.0,
- child: new Row(children: <Widget>[
- new Leaf(key: const GlobalObjectKey<_LeafState>(1), child: const Placeholder()),
- new Leaf(key: const GlobalObjectKey<_LeafState>(2), child: const Placeholder()),
- ]),
+ await tester.pumpWidget(new Directionality(
+ textDirection: TextDirection.ltr,
+ child: new ListView(
+ addAutomaticKeepAlives: false,
+ addRepaintBoundaries: false,
+ children: <Widget>[
+ new AutomaticKeepAlive(
+ child: new Container(
+ height: 400.0,
+ child: new Row(children: <Widget>[
+ new Leaf(key: const GlobalObjectKey<_LeafState>(1), child: const Placeholder()),
+ new Leaf(key: const GlobalObjectKey<_LeafState>(2), child: const Placeholder()),
+ ]),
+ ),
),
- ),
- new AutomaticKeepAlive(
- child: new Container(
- height: 400.0,
- child: new Row(children: <Widget>[
- ]),
+ new AutomaticKeepAlive(
+ child: new Container(
+ height: 400.0,
+ child: new Row(children: <Widget>[
+ ]),
+ ),
),
- ),
- new AutomaticKeepAlive(
- child: new Container(
- height: 400.0,
- child: new Row(children: <Widget>[
- new Leaf(key: const GlobalObjectKey<_LeafState>(3), child: const Placeholder()),
- new Leaf(key: const GlobalObjectKey<_LeafState>(4), child: const Placeholder()),
- new Leaf(key: const GlobalObjectKey<_LeafState>(5), child: const Placeholder()),
- new Leaf(key: const GlobalObjectKey<_LeafState>(0), child: const Placeholder()),
- ]),
+ new AutomaticKeepAlive(
+ child: new Container(
+ height: 400.0,
+ child: new Row(children: <Widget>[
+ new Leaf(key: const GlobalObjectKey<_LeafState>(3), child: const Placeholder()),
+ new Leaf(key: const GlobalObjectKey<_LeafState>(4), child: const Placeholder()),
+ new Leaf(key: const GlobalObjectKey<_LeafState>(5), child: const Placeholder()),
+ new Leaf(key: const GlobalObjectKey<_LeafState>(0), child: const Placeholder()),
+ ]),
+ ),
),
- ),
- ],
+ ],
+ ),
));
await tester.pump(); // Sometimes AutomaticKeepAlive needs an extra pump to clean things up.
expect(find.byKey(const GlobalObjectKey<_LeafState>(1)), findsOneWidget);
diff --git a/packages/flutter/test/widgets/clamp_overscrolls_test.dart b/packages/flutter/test/widgets/clamp_overscrolls_test.dart
index 20a3e2c..6dffdea 100644
--- a/packages/flutter/test/widgets/clamp_overscrolls_test.dart
+++ b/packages/flutter/test/widgets/clamp_overscrolls_test.dart
@@ -19,6 +19,7 @@
height: 650.0,
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
+ textDirection: TextDirection.ltr,
children: <Widget>[
const SizedBox(height: 100.0, child: const Text('top')),
new Expanded(child: new Container()),
diff --git a/packages/flutter/test/widgets/column_test.dart b/packages/flutter/test/widgets/column_test.dart
index 36b6916..bfe832b 100644
--- a/packages/flutter/test/widgets/column_test.dart
+++ b/packages/flutter/test/widgets/column_test.dart
@@ -7,6 +7,8 @@
import 'package:flutter/widgets.dart';
void main() {
+ // DOWN (default)
+
testWidgets('Column with one flexible child', (WidgetTester tester) async {
final Key columnKey = const Key('column');
final Key child0Key = const Key('child0');
@@ -386,4 +388,397 @@
expect(renderBox.size.width, equals(0.0));
expect(renderBox.size.height, equals(100.0));
});
+
+
+ // UP
+
+ testWidgets('Column with one flexible child', (WidgetTester tester) async {
+ final Key columnKey = const Key('column');
+ final Key child0Key = const Key('child0');
+ final Key child1Key = const Key('child1');
+ final Key child2Key = const Key('child2');
+
+ // Default is MainAxisSize.max so the Column should be as high as the test: 600.
+ // Default is MainAxisAlignment.start so children so the children's
+ // bottom edges should be at 0, 100, 500 from bottom, child2's height should be 400.
+ await tester.pumpWidget(new Center(
+ child: new Column(
+ key: columnKey,
+ verticalDirection: VerticalDirection.up,
+ children: <Widget>[
+ new Container(key: child0Key, width: 100.0, height: 100.0),
+ new Expanded(child: new Container(key: child1Key, width: 100.0, height: 100.0)),
+ new Container(key: child2Key, width: 100.0, height: 100.0),
+ ]
+ )
+ ));
+
+ RenderBox renderBox;
+ BoxParentData boxParentData;
+
+ renderBox = tester.renderObject(find.byKey(columnKey));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(600.0));
+
+ renderBox = tester.renderObject(find.byKey(child0Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dy, equals(500.0));
+
+ renderBox = tester.renderObject(find.byKey(child1Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(400.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dy, equals(100.0));
+
+ renderBox = tester.renderObject(find.byKey(child2Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dy, equals(0.0));
+ });
+
+ testWidgets('Column with default main axis parameters', (WidgetTester tester) async {
+ final Key columnKey = const Key('column');
+ final Key child0Key = const Key('child0');
+ final Key child1Key = const Key('child1');
+ final Key child2Key = const Key('child2');
+
+ // Default is MainAxisSize.max so the Column should be as high as the test: 600.
+ // Default is MainAxisAlignment.start so children so the children's
+ // bottom edges should be at 0, 100, 200 from bottom
+ await tester.pumpWidget(new Center(
+ child: new Column(
+ key: columnKey,
+ verticalDirection: VerticalDirection.up,
+ children: <Widget>[
+ new Container(key: child0Key, width: 100.0, height: 100.0),
+ new Container(key: child1Key, width: 100.0, height: 100.0),
+ new Container(key: child2Key, width: 100.0, height: 100.0),
+ ]
+ )
+ ));
+
+ RenderBox renderBox;
+ BoxParentData boxParentData;
+
+ renderBox = tester.renderObject(find.byKey(columnKey));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(600.0));
+
+ renderBox = tester.renderObject(find.byKey(child0Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dy, equals(500.0));
+
+ renderBox = tester.renderObject(find.byKey(child1Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dy, equals(400.0));
+
+ renderBox = tester.renderObject(find.byKey(child2Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dy, equals(300.0));
+ });
+
+ testWidgets('Column with MainAxisAlignment.center', (WidgetTester tester) async {
+ final Key columnKey = const Key('column');
+ final Key child0Key = const Key('child0');
+ final Key child1Key = const Key('child1');
+
+ // Default is MainAxisSize.max so the Column should be as high as the test: 600.
+ // The 100x100 children's bottom edges should be at 200, 300 from bottom
+ await tester.pumpWidget(new Center(
+ child: new Column(
+ key: columnKey,
+ mainAxisAlignment: MainAxisAlignment.center,
+ verticalDirection: VerticalDirection.up,
+ children: <Widget>[
+ new Container(key: child0Key, width: 100.0, height: 100.0),
+ new Container(key: child1Key, width: 100.0, height: 100.0),
+ ]
+ )
+ ));
+
+ RenderBox renderBox;
+ BoxParentData boxParentData;
+
+ renderBox = tester.renderObject(find.byKey(columnKey));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(600.0));
+
+ renderBox = tester.renderObject(find.byKey(child0Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dy, equals(300.0));
+
+ renderBox = tester.renderObject(find.byKey(child1Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dy, equals(200.0));
+ });
+
+ testWidgets('Column with MainAxisAlignment.end', (WidgetTester tester) async {
+ final Key columnKey = const Key('column');
+ final Key child0Key = const Key('child0');
+ final Key child1Key = const Key('child1');
+ final Key child2Key = const Key('child2');
+
+ // Default is MainAxisSize.max so the Column should be as high as the test: 600.
+ // The 100x100 children's bottom edges should be at 300, 400, 500 from bottom.
+ await tester.pumpWidget(new Center(
+ child: new Column(
+ key: columnKey,
+ mainAxisAlignment: MainAxisAlignment.end,
+ verticalDirection: VerticalDirection.up,
+ children: <Widget>[
+ new Container(key: child0Key, width: 100.0, height: 100.0),
+ new Container(key: child1Key, width: 100.0, height: 100.0),
+ new Container(key: child2Key, width: 100.0, height: 100.0),
+ ]
+ )
+ ));
+
+ RenderBox renderBox;
+ BoxParentData boxParentData;
+
+ renderBox = tester.renderObject(find.byKey(columnKey));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(600.0));
+
+ renderBox = tester.renderObject(find.byKey(child0Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dy, equals(200.0));
+
+ renderBox = tester.renderObject(find.byKey(child1Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dy, equals(100.0));
+
+ renderBox = tester.renderObject(find.byKey(child2Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dy, equals(0.0));
+ });
+
+ testWidgets('Column with MainAxisAlignment.spaceBetween', (WidgetTester tester) async {
+ final Key columnKey = const Key('column');
+ final Key child0Key = const Key('child0');
+ final Key child1Key = const Key('child1');
+ final Key child2Key = const Key('child2');
+
+ // Default is MainAxisSize.max so the Column should be as high as the test: 600.
+ // The 100x100 children's bottom edges should be at 0, 250, 500 from bottom
+ await tester.pumpWidget(new Center(
+ child: new Column(
+ key: columnKey,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ verticalDirection: VerticalDirection.up,
+ children: <Widget>[
+ new Container(key: child0Key, width: 100.0, height: 100.0),
+ new Container(key: child1Key, width: 100.0, height: 100.0),
+ new Container(key: child2Key, width: 100.0, height: 100.0),
+ ]
+ )
+ ));
+
+ RenderBox renderBox;
+ BoxParentData boxParentData;
+
+ renderBox = tester.renderObject(find.byKey(columnKey));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(600.0));
+
+ renderBox = tester.renderObject(find.byKey(child0Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dy, equals(500.0));
+
+ renderBox = tester.renderObject(find.byKey(child1Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dy, equals(250.0));
+
+ renderBox = tester.renderObject(find.byKey(child2Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dy, equals(0.0));
+ });
+
+ testWidgets('Column with MainAxisAlignment.spaceAround', (WidgetTester tester) async {
+ final Key columnKey = const Key('column');
+ final Key child0Key = const Key('child0');
+ final Key child1Key = const Key('child1');
+ final Key child2Key = const Key('child2');
+ final Key child3Key = const Key('child3');
+
+ // Default is MainAxisSize.max so the Column should be as high as the test: 600.
+ // The 100x100 children's bottom edges should be at 25, 175, 325, 475 from bottom
+ await tester.pumpWidget(new Center(
+ child: new Column(
+ key: columnKey,
+ mainAxisAlignment: MainAxisAlignment.spaceAround,
+ verticalDirection: VerticalDirection.up,
+ children: <Widget>[
+ new Container(key: child0Key, width: 100.0, height: 100.0),
+ new Container(key: child1Key, width: 100.0, height: 100.0),
+ new Container(key: child2Key, width: 100.0, height: 100.0),
+ new Container(key: child3Key, width: 100.0, height: 100.0),
+ ]
+ )
+ ));
+
+ RenderBox renderBox;
+ BoxParentData boxParentData;
+
+ renderBox = tester.renderObject(find.byKey(columnKey));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(600.0));
+
+ renderBox = tester.renderObject(find.byKey(child0Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dy, equals(500.0 - 25.0));
+
+ renderBox = tester.renderObject(find.byKey(child1Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dy, equals(500.0 - 175.0));
+
+ renderBox = tester.renderObject(find.byKey(child2Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dy, equals(500.0 - 325.0));
+
+ renderBox = tester.renderObject(find.byKey(child3Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dy, equals(500.0 - 475.0));
+ });
+
+ testWidgets('Column with MainAxisAlignment.spaceEvenly', (WidgetTester tester) async {
+ final Key columnKey = const Key('column');
+ final Key child0Key = const Key('child0');
+ final Key child1Key = const Key('child1');
+ final Key child2Key = const Key('child2');
+
+ // Default is MainAxisSize.max so the Column should be as high as the test: 600.
+ // The 100x20 children's bottom edges should be at 135, 290, 445 from bottom
+ await tester.pumpWidget(new Center(
+ child: new Column(
+ key: columnKey,
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ verticalDirection: VerticalDirection.up,
+ children: <Widget>[
+ new Container(key: child0Key, width: 100.0, height: 20.0),
+ new Container(key: child1Key, width: 100.0, height: 20.0),
+ new Container(key: child2Key, width: 100.0, height: 20.0),
+ ]
+ )
+ ));
+
+ RenderBox renderBox;
+ BoxParentData boxParentData;
+
+ renderBox = tester.renderObject(find.byKey(columnKey));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(600.0));
+
+ renderBox = tester.renderObject(find.byKey(child0Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(20.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dy, equals(600.0 - 135.0 - 20.0));
+
+ renderBox = tester.renderObject(find.byKey(child1Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(20.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dy, equals(600.0 - 290.0 - 20.0));
+
+ renderBox = tester.renderObject(find.byKey(child2Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(20.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dy, equals(600.0 - 445.0 - 20.0));
+ });
+
+ testWidgets('Column and MainAxisSize.min', (WidgetTester tester) async {
+ final Key flexKey = const Key('flexKey');
+
+ // Default is MainAxisSize.max so the Column should be as high as the test: 600.
+ await tester.pumpWidget(new Center(
+ child: new Column(
+ key: flexKey,
+ verticalDirection: VerticalDirection.up,
+ children: <Widget>[
+ new Container(width: 100.0, height: 100.0),
+ new Container(width: 100.0, height: 150.0)
+ ]
+ )
+ ));
+ RenderBox renderBox = tester.renderObject(find.byKey(flexKey));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(600.0));
+
+ // Column with MainAxisSize.min without flexible children shrink wraps.
+ await tester.pumpWidget(new Center(
+ child: new Column(
+ key: flexKey,
+ mainAxisSize: MainAxisSize.min,
+ verticalDirection: VerticalDirection.up,
+ children: <Widget>[
+ new Container(width: 100.0, height: 100.0),
+ new Container(width: 100.0, height: 150.0)
+ ]
+ )
+ ));
+ renderBox = tester.renderObject(find.byKey(flexKey));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(250.0));
+ });
+
+ testWidgets('Column MainAxisSize.min layout at zero size', (WidgetTester tester) async {
+ final Key childKey = const Key('childKey');
+
+ await tester.pumpWidget(new Center(
+ child: new Container(
+ width: 0.0,
+ height: 0.0,
+ child: new Column(
+ mainAxisSize: MainAxisSize.min,
+ verticalDirection: VerticalDirection.up,
+ children: <Widget>[
+ new Container(
+ key: childKey,
+ width: 100.0,
+ height: 100.0
+ )
+ ]
+ )
+ )
+ ));
+
+ final RenderBox renderBox = tester.renderObject(find.byKey(childKey));
+ expect(renderBox.size.width, equals(0.0));
+ expect(renderBox.size.height, equals(100.0));
+ });
}
diff --git a/packages/flutter/test/widgets/container_test.dart b/packages/flutter/test/widgets/container_test.dart
index a29f72c..65b49fb 100644
--- a/packages/flutter/test/widgets/container_test.dart
+++ b/packages/flutter/test/widgets/container_test.dart
@@ -59,7 +59,8 @@
' │ parentData: offset=Offset(0.0, 0.0) (can use size)\n'
' │ constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.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'
' └─child: RenderConstrainedBox#00000 relayoutBoundary=up2\n'
' │ creator: ConstrainedBox ← Padding ← Container ← Align ← [root]\n'
@@ -98,7 +99,8 @@
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=53.0, h=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'
' └─child: RenderPositionedBox#00000\n'
' │ creator: Align ← Padding ← DecoratedBox ← DecoratedBox ←\n'
@@ -106,7 +108,7 @@
' │ parentData: offset=Offset(7.0, 7.0) (can use size)\n'
' │ constraints: BoxConstraints(w=39.0, h=64.0)\n'
' │ size: Size(39.0, 64.0)\n'
- ' │ alignment: FractionalOffset(1.0, 1.0)\n'
+ ' │ alignment: FractionalOffset.bottomRight\n'
' │ widthFactor: expand\n'
' │ heightFactor: expand\n'
' │\n'
diff --git a/packages/flutter/test/widgets/flex_test.dart b/packages/flutter/test/widgets/flex_test.dart
index 147ec58..126e5ef 100644
--- a/packages/flutter/test/widgets/flex_test.dart
+++ b/packages/flutter/test/widgets/flex_test.dart
@@ -47,6 +47,7 @@
testWidgets('Flexible defaults to loose', (WidgetTester tester) async {
await tester.pumpWidget(
new Row(
+ textDirection: TextDirection.ltr,
children: <Widget>[
const Flexible(child: const SizedBox(width: 100.0, height: 200.0)),
],
@@ -60,6 +61,7 @@
testWidgets('Can pass null for flex', (WidgetTester tester) async {
await tester.pumpWidget(
new Row(
+ textDirection: TextDirection.ltr,
children: <Widget>[
const Expanded(flex: null, child: const Text('one')),
const Flexible(flex: null, child: const Text('two')),
diff --git a/packages/flutter/test/widgets/focus_test.dart b/packages/flutter/test/widgets/focus_test.dart
index 8ec6ca6..282d1f2 100644
--- a/packages/flutter/test/widgets/focus_test.dart
+++ b/packages/flutter/test/widgets/focus_test.dart
@@ -145,6 +145,7 @@
node: parentFocusScope,
autofocus: true,
child: new Row(
+ textDirection: TextDirection.ltr,
children: <Widget>[
const TestFocusable(
no: 'a',
@@ -193,6 +194,7 @@
new FocusScope(
node: parentFocusScope,
child: new Row(
+ textDirection: TextDirection.ltr,
children: <Widget>[
const TestFocusable(
no: 'a',
@@ -218,6 +220,7 @@
new FocusScope(
node: parentFocusScope,
child: new Row(
+ textDirection: TextDirection.ltr,
children: <Widget>[
const TestFocusable(
no: 'a',
diff --git a/packages/flutter/test/widgets/framework_test.dart b/packages/flutter/test/widgets/framework_test.dart
index df97306..bfa0119 100644
--- a/packages/flutter/test/widgets/framework_test.dart
+++ b/packages/flutter/test/widgets/framework_test.dart
@@ -486,7 +486,7 @@
expect(
element.toStringDeep(),
equalsIgnoringHashCodes(
- 'Column-[GlobalKey#00000](renderObject: RenderFlex#00000)\n'
+ 'Column-[GlobalKey#00000](direction: vertical, mainAxisAlignment: start, crossAxisAlignment: center, renderObject: RenderFlex#00000)\n'
'├Container\n'
'│└LimitedBox(maxWidth: 0.0, maxHeight: 0.0, renderObject: RenderLimitedBox#00000 relayoutBoundary=up1)\n'
'│ └ConstrainedBox(BoxConstraints(biggest), renderObject: RenderConstrainedBox#00000 relayoutBoundary=up2)\n'
diff --git a/packages/flutter/test/widgets/global_keys_duplicated_test.dart b/packages/flutter/test/widgets/global_keys_duplicated_test.dart
index 442553a..7a5e55c 100644
--- a/packages/flutter/test/widgets/global_keys_duplicated_test.dart
+++ b/packages/flutter/test/widgets/global_keys_duplicated_test.dart
@@ -24,10 +24,13 @@
});
testWidgets('GlobalKey children of two nodes', (WidgetTester tester) async {
- await tester.pumpWidget(new Row(children: <Widget>[
- new Container(child: new Container(key: const GlobalObjectKey(0))),
- new Container(child: new Container(key: const GlobalObjectKey(0))),
- ]));
+ await tester.pumpWidget(new Row(
+ textDirection: TextDirection.ltr,
+ 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();
expect(error, isFlutterError);
expect(error.toString(), startsWith('Multiple widgets used the same GlobalKey.\n'));
@@ -38,10 +41,13 @@
});
testWidgets('GlobalKey children of two different nodes', (WidgetTester tester) async {
- await tester.pumpWidget(new Row(children: <Widget>[
- new Container(child: new Container(key: const GlobalObjectKey(0))),
- new Container(key: const Key('x'), child: new Container(key: const GlobalObjectKey(0))),
- ]));
+ await tester.pumpWidget(new Row(
+ textDirection: TextDirection.ltr,
+ 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();
expect(error, isFlutterError);
expect(error.toString(), startsWith('Multiple widgets used the same GlobalKey.\n'));
@@ -55,17 +61,20 @@
testWidgets('GlobalKey children of two nodes', (WidgetTester tester) async {
StateSetter nestedSetState;
bool flag = false;
- await tester.pumpWidget(new Row(children: <Widget>[
- new Container(child: new Container(key: const GlobalObjectKey(0))),
- new Container(child: new StatefulBuilder(
- builder: (BuildContext context, StateSetter setState) {
- nestedSetState = setState;
- if (flag)
- return new Container(key: const GlobalObjectKey(0));
- return new Container();
- },
- )),
- ]));
+ await tester.pumpWidget(new Row(
+ textDirection: TextDirection.ltr,
+ children: <Widget>[
+ new Container(child: new Container(key: const GlobalObjectKey(0))),
+ new Container(child: new StatefulBuilder(
+ builder: (BuildContext context, StateSetter setState) {
+ nestedSetState = setState;
+ if (flag)
+ return new Container(key: const GlobalObjectKey(0));
+ return new Container();
+ },
+ )),
+ ],
+ ));
nestedSetState(() { flag = true; });
await tester.pump();
final dynamic error = tester.takeException();
diff --git a/packages/flutter/test/widgets/image_test.dart b/packages/flutter/test/widgets/image_test.dart
index 8c9be1f..7a487d8 100644
--- a/packages/flutter/test/widgets/image_test.dart
+++ b/packages/flutter/test/widgets/image_test.dart
@@ -235,6 +235,7 @@
// of the Image changes and the MediaQuery widgets do not.
await tester.pumpWidget(
new Row(
+ textDirection: TextDirection.ltr,
children: <Widget> [
new MediaQuery(
key: mediaQueryKey2,
@@ -263,6 +264,7 @@
await tester.pumpWidget(
new Row(
+ textDirection: TextDirection.ltr,
children: <Widget> [
new MediaQuery(
key: mediaQueryKey2,
diff --git a/packages/flutter/test/widgets/layout_builder_and_parent_data_test.dart b/packages/flutter/test/widgets/layout_builder_and_parent_data_test.dart
index 7f25030..22929b5 100644
--- a/packages/flutter/test/widgets/layout_builder_and_parent_data_test.dart
+++ b/packages/flutter/test/widgets/layout_builder_and_parent_data_test.dart
@@ -29,6 +29,7 @@
@override
Widget build(BuildContext context) {
return new Row(
+ textDirection: TextDirection.ltr,
children: <Widget>[
new SizedBox(
height: _flag ? 50.0 : 100.0,
diff --git a/packages/flutter/test/widgets/layout_builder_mutations_test.dart b/packages/flutter/test/widgets/layout_builder_mutations_test.dart
index 1eff184..f0d238f 100644
--- a/packages/flutter/test/widgets/layout_builder_mutations_test.dart
+++ b/packages/flutter/test/widgets/layout_builder_mutations_test.dart
@@ -25,38 +25,44 @@
testWidgets('Moving a global key from another LayoutBuilder at layout time', (WidgetTester tester) async {
final GlobalKey victimKey = new GlobalKey();
- await tester.pumpWidget(new Row(children: <Widget>[
- new Wrapper(
- child: new LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
- return const SizedBox();
- }),
- ),
- new Wrapper(
- child: new Wrapper(
+ await tester.pumpWidget(new Row(
+ textDirection: TextDirection.ltr,
+ children: <Widget>[
+ new Wrapper(
+ child: new LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
+ return const SizedBox();
+ }),
+ ),
+ new Wrapper(
+ child: new Wrapper(
+ child: new LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
+ return new Wrapper(
+ child: new SizedBox(key: victimKey)
+ );
+ })
+ )
+ ),
+ ],
+ ));
+
+ await tester.pumpWidget(new Row(
+ textDirection: TextDirection.ltr,
+ children: <Widget>[
+ new Wrapper(
child: new LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
return new Wrapper(
child: new SizedBox(key: victimKey)
);
})
- )
- ),
- ]));
-
- await tester.pumpWidget(new Row(children: <Widget>[
- new Wrapper(
- child: new LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
- return new Wrapper(
- child: new SizedBox(key: victimKey)
- );
- })
- ),
- new Wrapper(
- child: new Wrapper(
- child: new LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
- return const SizedBox();
- })
- )
- ),
- ]));
+ ),
+ new Wrapper(
+ child: new Wrapper(
+ child: new LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
+ return const SizedBox();
+ })
+ )
+ ),
+ ],
+ ));
});
}
diff --git a/packages/flutter/test/widgets/localizations_test.dart b/packages/flutter/test/widgets/localizations_test.dart
index ac718ae..b67f1fd 100644
--- a/packages/flutter/test/widgets/localizations_test.dart
+++ b/packages/flutter/test/widgets/localizations_test.dart
@@ -42,6 +42,9 @@
shouldReloadValues.add(prefix != old.prefix);
return prefix != old.prefix;
}
+
+ @override
+ String toString() => '$runtimeType($prefix)';
}
class AsyncTestLocalizationsDelegate extends LocalizationsDelegate<TestLocalizations> {
@@ -58,6 +61,9 @@
shouldReloadValues.add(prefix != old.prefix);
return prefix != old.prefix;
}
+
+ @override
+ String toString() => '$runtimeType($prefix)';
}
class MoreLocalizations {
@@ -289,6 +295,7 @@
locale: const Locale('en', 'GB'),
delegates: <LocalizationsDelegate<dynamic>>[
new SyncTestLocalizationsDelegate(),
+ new DefaultWidgetsLocalizationsDelegate(),
],
// Create a new context within the en_GB Localization
child: new Builder(
@@ -359,6 +366,7 @@
expect(find.text('A: ---en_US'), findsOneWidget);
expect(find.text('B: en_US'), findsOneWidget);
expect(modifiedDelegate.shouldReloadValues, <bool>[true]);
+ expect(originalDelegate.shouldReloadValues, <bool>[]);
});
testWidgets('Localizations async delegate shouldReload returns true', (WidgetTester tester) async {
@@ -410,3 +418,16 @@
});
}
+
+// Same as _WidgetsLocalizationsDelegate in widgets/app.dart
+class DefaultWidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
+ const DefaultWidgetsLocalizationsDelegate();
+
+ @override
+ Future<WidgetsLocalizations> load(Locale locale) {
+ return new SynchronousFuture<WidgetsLocalizations>(const WidgetsLocalizations());
+ }
+
+ @override
+ bool shouldReload(DefaultWidgetsLocalizationsDelegate old) => false;
+}
diff --git a/packages/flutter/test/widgets/nested_scroll_view_test.dart b/packages/flutter/test/widgets/nested_scroll_view_test.dart
index a469654..8d1970b 100644
--- a/packages/flutter/test/widgets/nested_scroll_view_test.dart
+++ b/packages/flutter/test/widgets/nested_scroll_view_test.dart
@@ -23,71 +23,74 @@
Widget buildTest({ ScrollController controller, String title:'TTTTTTTT' }) {
return new MediaQuery(
data: const MediaQueryData(),
- child: new Scaffold(
- body: new DefaultTabController(
- length: 4,
- child: new NestedScrollView(
- controller: controller,
- headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
- return <Widget>[
- new SliverAppBar(
- title: new Text(title),
- pinned: true,
- expandedHeight: 200.0,
- forceElevated: innerBoxIsScrolled,
- bottom: new TabBar(
- tabs: const <Tab>[
- const Tab(text: 'AA'),
- const Tab(text: 'BB'),
- const Tab(text: 'CC'),
- const Tab(text: 'DD'),
+ child: new Directionality(
+ textDirection: TextDirection.ltr,
+ child: new Scaffold(
+ body: new DefaultTabController(
+ length: 4,
+ child: new NestedScrollView(
+ controller: controller,
+ headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
+ return <Widget>[
+ new SliverAppBar(
+ title: new Text(title),
+ pinned: true,
+ expandedHeight: 200.0,
+ forceElevated: innerBoxIsScrolled,
+ bottom: new TabBar(
+ tabs: const <Tab>[
+ const Tab(text: 'AA'),
+ const Tab(text: 'BB'),
+ const Tab(text: 'CC'),
+ const Tab(text: 'DD'),
+ ],
+ ),
+ ),
+ ];
+ },
+ body: new TabBarView(
+ children: <Widget>[
+ new ListView(
+ children: <Widget>[
+ new Container(
+ height: 300.0,
+ child: const Text('aaa1'),
+ ),
+ new Container(
+ height: 200.0,
+ child: const Text('aaa2'),
+ ),
+ new Container(
+ height: 100.0,
+ child: const Text('aaa3'),
+ ),
+ new Container(
+ height: 50.0,
+ child: const Text('aaa4'),
+ ),
],
),
- ),
- ];
- },
- body: new TabBarView(
- children: <Widget>[
- new ListView(
- children: <Widget>[
- new Container(
- height: 300.0,
- child: const Text('aaa1'),
- ),
- new Container(
- height: 200.0,
- child: const Text('aaa2'),
- ),
- new Container(
- height: 100.0,
- child: const Text('aaa3'),
- ),
- new Container(
- height: 50.0,
- child: const Text('aaa4'),
- ),
- ],
- ),
- new ListView(
- children: <Widget>[
- new Container(
- height: 100.0,
- child: const Text('bbb1'),
- ),
- ],
- ),
- new Container(
- child: const Center(child: const Text('ccc1')),
- ),
- new ListView(
- children: <Widget>[
- new Container(
- height: 10000.0,
- child: const Text('ddd1'),
- ),
- ],
- ),
- ],
+ new ListView(
+ children: <Widget>[
+ new Container(
+ height: 100.0,
+ child: const Text('bbb1'),
+ ),
+ ],
+ ),
+ new Container(
+ child: const Center(child: const Text('ccc1')),
+ ),
+ new ListView(
+ children: <Widget>[
+ new Container(
+ height: 10000.0,
+ child: const Text('ddd1'),
+ ),
+ ],
+ ),
+ ],
+ ),
),
),
),
diff --git a/packages/flutter/test/widgets/overflow_box_test.dart b/packages/flutter/test/widgets/overflow_box_test.dart
index b40b938..7c7d384 100644
--- a/packages/flutter/test/widgets/overflow_box_test.dart
+++ b/packages/flutter/test/widgets/overflow_box_test.dart
@@ -42,7 +42,7 @@
.where((DiagnosticsNode n) => !n.hidden)
.map((DiagnosticsNode n) => n.toString()).toList();
expect(description, <String>[
- 'alignment: FractionalOffset(0.5, 0.5)',
+ 'alignment: FractionalOffset.center',
'minWidth: 1.0',
'maxWidth: 2.0',
'minHeight: 3.0',
diff --git a/packages/flutter/test/widgets/overlay_test.dart b/packages/flutter/test/widgets/overlay_test.dart
index aa28b89..9c1e3df 100644
--- a/packages/flutter/test/widgets/overlay_test.dart
+++ b/packages/flutter/test/widgets/overlay_test.dart
@@ -43,7 +43,7 @@
' ╎ │ size)\n'
' ╎ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' ╎ │ size: Size(800.0, 600.0)\n'
- ' ╎ │ alignment: FractionalOffset(0.0, 0.0)\n'
+ ' ╎ │ alignment: FractionalOffset.topLeft\n'
' ╎ │ fit: expand\n'
' ╎ │ overflow: clip\n'
' ╎ │\n'
@@ -112,7 +112,7 @@
' ╎ │ size)\n'
' ╎ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' ╎ │ size: Size(800.0, 600.0)\n'
- ' ╎ │ alignment: FractionalOffset(0.0, 0.0)\n'
+ ' ╎ │ alignment: FractionalOffset.topLeft\n'
' ╎ │ fit: expand\n'
' ╎ │ overflow: clip\n'
' ╎ │\n'
diff --git a/packages/flutter/test/widgets/reparent_state_test.dart b/packages/flutter/test/widgets/reparent_state_test.dart
index 6f3ed6d..b5cd712 100644
--- a/packages/flutter/test/widgets/reparent_state_test.dart
+++ b/packages/flutter/test/widgets/reparent_state_test.dart
@@ -335,6 +335,7 @@
await tester.pumpWidget(
new Row(
+ textDirection: TextDirection.ltr,
children: <Widget>[
new StateMarker(
key: key1,
@@ -352,6 +353,7 @@
await tester.pumpWidget(
new Row(
+ textDirection: TextDirection.ltr,
children: <Widget>[
new StateMarker(
key: key2,
diff --git a/packages/flutter/test/widgets/rotated_box_test.dart b/packages/flutter/test/widgets/rotated_box_test.dart
index 65b0e3f..3e003ca 100644
--- a/packages/flutter/test/widgets/rotated_box_test.dart
+++ b/packages/flutter/test/widgets/rotated_box_test.dart
@@ -16,6 +16,7 @@
key: rotatedBoxKey,
quarterTurns: 1,
child: new Row(
+ textDirection: TextDirection.ltr,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new GestureDetector(
diff --git a/packages/flutter/test/widgets/row_test.dart b/packages/flutter/test/widgets/row_test.dart
index c2b21ab..1220e5d 100644
--- a/packages/flutter/test/widgets/row_test.dart
+++ b/packages/flutter/test/widgets/row_test.dart
@@ -2,12 +2,316 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+class OrderPainter extends CustomPainter {
+ const OrderPainter(this.index);
+
+ final int index;
+
+ static List<int> log = <int>[];
+
+ @override
+ void paint(Canvas canvas, Size size) {
+ log.add(index);
+ }
+
+ @override
+ bool shouldRepaint(OrderPainter old) => false;
+}
+
+Widget log(int index) => new CustomPaint(painter: new OrderPainter(index));
void main() {
- testWidgets('Row with one Flexible child', (WidgetTester tester) async {
+ // NO DIRECTION
+
+ testWidgets('Row with one Flexible child - no textDirection', (WidgetTester tester) async {
+ OrderPainter.log.clear();
+ final Key rowKey = const Key('row');
+ final Key child0Key = const Key('child0');
+ final Key child1Key = const Key('child1');
+ final Key child2Key = const Key('child2');
+
+ final FlutterExceptionHandler oldHandler = FlutterError.onError;
+ dynamic exception;
+ FlutterError.onError = (FlutterErrorDetails details) {
+ exception ??= details.exception;
+ };
+
+ // Default is MainAxisAlignment.start so this should fail, asking for a direction.
+ await tester.pumpWidget(new Center(
+ child: new Row(
+ key: rowKey,
+ children: <Widget>[
+ new Container(key: child0Key, width: 100.0, height: 100.0, child: log(1)),
+ new Expanded(child: new Container(key: child1Key, width: 100.0, height: 100.0, child: log(2))),
+ new Container(key: child2Key, width: 100.0, height: 100.0, child: log(3)),
+ ],
+ ),
+ ));
+
+ FlutterError.onError = oldHandler;
+ expect(exception, isAssertionError);
+ expect(exception.toString(), contains('textDirection'));
+ expect(OrderPainter.log, <int>[]);
+ });
+
+ testWidgets('Row with default main axis parameters - no textDirection', (WidgetTester tester) async {
+ OrderPainter.log.clear();
+ final Key rowKey = const Key('row');
+ final Key child0Key = const Key('child0');
+ final Key child1Key = const Key('child1');
+ final Key child2Key = const Key('child2');
+
+ final FlutterExceptionHandler oldHandler = FlutterError.onError;
+ dynamic exception;
+ FlutterError.onError = (FlutterErrorDetails details) {
+ exception ??= details.exception;
+ };
+
+ // Default is MainAxisAlignment.start so this should fail too.
+ await tester.pumpWidget(new Center(
+ child: new Row(
+ key: rowKey,
+ children: <Widget>[
+ new Container(key: child0Key, width: 100.0, height: 100.0, child: log(1)),
+ new Container(key: child1Key, width: 100.0, height: 100.0, child: log(2)),
+ new Container(key: child2Key, width: 100.0, height: 100.0, child: log(3)),
+ ],
+ ),
+ ));
+
+ FlutterError.onError = oldHandler;
+ expect(exception, isAssertionError);
+ expect(exception.toString(), contains('textDirection'));
+ expect(OrderPainter.log, <int>[]);
+ });
+
+ testWidgets('Row with MainAxisAlignment.center - no textDirection', (WidgetTester tester) async {
+ OrderPainter.log.clear();
+ final Key rowKey = const Key('row');
+ final Key child0Key = const Key('child0');
+ final Key child1Key = const Key('child1');
+
+ final FlutterExceptionHandler oldHandler = FlutterError.onError;
+ dynamic exception;
+ FlutterError.onError = (FlutterErrorDetails details) {
+ exception ??= details.exception;
+ };
+
+ // More than one child, so it's not clear what direction to lay out in: should fail.
+ await tester.pumpWidget(new Center(
+ child: new Row(
+ key: rowKey,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[
+ new Container(key: child0Key, width: 100.0, height: 100.0, child: log(1)),
+ new Container(key: child1Key, width: 100.0, height: 100.0, child: log(2)),
+ ],
+ ),
+ ));
+
+ FlutterError.onError = oldHandler;
+ expect(exception, isAssertionError);
+ expect(exception.toString(), contains('textDirection'));
+ expect(OrderPainter.log, <int>[]);
+ });
+
+ testWidgets('Row with MainAxisAlignment.end - no textDirection', (WidgetTester tester) async {
+ OrderPainter.log.clear();
+ final Key rowKey = const Key('row');
+ final Key child0Key = const Key('child0');
+ final Key child1Key = const Key('child1');
+ final Key child2Key = const Key('child2');
+
+ final FlutterExceptionHandler oldHandler = FlutterError.onError;
+ dynamic exception;
+ FlutterError.onError = (FlutterErrorDetails details) {
+ exception ??= details.exception;
+ };
+
+ // No direction so this should fail, asking for a direction.
+ await tester.pumpWidget(new Center(
+ child: new Row(
+ key: rowKey,
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: <Widget>[
+ new Container(key: child0Key, width: 100.0, height: 100.0, child: log(1)),
+ new Container(key: child1Key, width: 100.0, height: 100.0, child: log(2)),
+ new Container(key: child2Key, width: 100.0, height: 100.0, child: log(3)),
+ ],
+ ),
+ ));
+
+ FlutterError.onError = oldHandler;
+ expect(exception, isAssertionError);
+ expect(exception.toString(), contains('textDirection'));
+ expect(OrderPainter.log, <int>[]);
+ });
+
+ testWidgets('Row with MainAxisAlignment.spaceBetween - no textDirection', (WidgetTester tester) async {
+ OrderPainter.log.clear();
+ final Key rowKey = const Key('row');
+ final Key child0Key = const Key('child0');
+ final Key child1Key = const Key('child1');
+ final Key child2Key = const Key('child2');
+
+ final FlutterExceptionHandler oldHandler = FlutterError.onError;
+ dynamic exception;
+ FlutterError.onError = (FlutterErrorDetails details) {
+ exception ??= details.exception;
+ };
+
+ // More than one child, so it's not clear what direction to lay out in: should fail.
+ await tester.pumpWidget(new Center(
+ child: new Row(
+ key: rowKey,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: <Widget>[
+ new Container(key: child0Key, width: 100.0, height: 100.0, child: log(1)),
+ new Container(key: child1Key, width: 100.0, height: 100.0, child: log(2)),
+ new Container(key: child2Key, width: 100.0, height: 100.0, child: log(3)),
+ ],
+ ),
+ ));
+
+ FlutterError.onError = oldHandler;
+ expect(exception, isAssertionError);
+ expect(exception.toString(), contains('textDirection'));
+ expect(OrderPainter.log, <int>[]);
+ });
+
+ testWidgets('Row with MainAxisAlignment.spaceAround - no textDirection', (WidgetTester tester) async {
+ OrderPainter.log.clear();
+ final Key rowKey = const Key('row');
+ final Key child0Key = const Key('child0');
+ final Key child1Key = const Key('child1');
+ final Key child2Key = const Key('child2');
+ final Key child3Key = const Key('child3');
+
+ final FlutterExceptionHandler oldHandler = FlutterError.onError;
+ dynamic exception;
+ FlutterError.onError = (FlutterErrorDetails details) {
+ exception ??= details.exception;
+ };
+
+ // More than one child, so it's not clear what direction to lay out in: should fail.
+ await tester.pumpWidget(new Center(
+ child: new Row(
+ key: rowKey,
+ mainAxisAlignment: MainAxisAlignment.spaceAround,
+ children: <Widget>[
+ new Container(key: child0Key, width: 100.0, height: 100.0, child: log(1)),
+ new Container(key: child1Key, width: 100.0, height: 100.0, child: log(2)),
+ new Container(key: child2Key, width: 100.0, height: 100.0, child: log(3)),
+ new Container(key: child3Key, width: 100.0, height: 100.0, child: log(4)),
+ ],
+ ),
+ ));
+
+ FlutterError.onError = oldHandler;
+ expect(exception, isAssertionError);
+ expect(exception.toString(), contains('textDirection'));
+ expect(OrderPainter.log, <int>[]);
+ });
+
+ testWidgets('Row with MainAxisAlignment.spaceEvenly - no textDirection', (WidgetTester tester) async {
+ OrderPainter.log.clear();
+ final Key rowKey = const Key('row');
+ final Key child0Key = const Key('child0');
+ final Key child1Key = const Key('child1');
+ final Key child2Key = const Key('child2');
+
+ final FlutterExceptionHandler oldHandler = FlutterError.onError;
+ dynamic exception;
+ FlutterError.onError = (FlutterErrorDetails details) {
+ exception ??= details.exception;
+ };
+
+ // More than one child, so it's not clear what direction to lay out in: should fail.
+ await tester.pumpWidget(new Center(
+ child: new Row(
+ key: rowKey,
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ children: <Widget>[
+ new Container(key: child0Key, width: 200.0, height: 100.0, child: log(1)),
+ new Container(key: child1Key, width: 200.0, height: 100.0, child: log(2)),
+ new Container(key: child2Key, width: 200.0, height: 100.0, child: log(3)),
+ ],
+ ),
+ ));
+
+ FlutterError.onError = oldHandler;
+ expect(exception, isAssertionError);
+ expect(exception.toString(), contains('textDirection'));
+ expect(OrderPainter.log, <int>[]);
+ });
+
+ testWidgets('Row and MainAxisSize.min - no textDirection', (WidgetTester tester) async {
+ OrderPainter.log.clear();
+ final Key rowKey = const Key('rowKey');
+ final Key child0Key = const Key('child0');
+ final Key child1Key = const Key('child1');
+
+ final FlutterExceptionHandler oldHandler = FlutterError.onError;
+ dynamic exception;
+ FlutterError.onError = (FlutterErrorDetails details) {
+ exception ??= details.exception;
+ };
+
+ // Default is MainAxisAlignment.start so this should fail, asking for a direction.
+ await tester.pumpWidget(new Center(
+ child: new Row(
+ key: rowKey,
+ mainAxisSize: MainAxisSize.min,
+ children: <Widget>[
+ new Container(key: child0Key, width: 100.0, height: 100.0, child: log(1)),
+ new Container(key: child1Key, width: 150.0, height: 100.0, child: log(2)),
+ ],
+ ),
+ ));
+
+ FlutterError.onError = oldHandler;
+ expect(exception, isAssertionError);
+ expect(exception.toString(), contains('textDirection'));
+ expect(OrderPainter.log, <int>[]);
+ });
+
+ testWidgets('Row MainAxisSize.min layout at zero size - no textDirection', (WidgetTester tester) async {
+ OrderPainter.log.clear();
+ final Key childKey = const Key('childKey');
+
+ await tester.pumpWidget(new Center(
+ child: new Container(
+ width: 0.0,
+ height: 0.0,
+ child: new Row(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[
+ new Container(
+ key: childKey,
+ width: 100.0,
+ height: 100.0,
+ ),
+ ],
+ ),
+ ),
+ ));
+
+ final RenderBox renderBox = tester.renderObject(find.byKey(childKey));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(0.0));
+ });
+
+
+ // LTR
+
+ testWidgets('Row with one Flexible child - LTR', (WidgetTester tester) async {
+ OrderPainter.log.clear();
final Key rowKey = const Key('row');
final Key child0Key = const Key('child0');
final Key child1Key = const Key('child1');
@@ -19,12 +323,13 @@
await tester.pumpWidget(new Center(
child: new Row(
key: rowKey,
+ textDirection: TextDirection.ltr,
children: <Widget>[
- new Container(key: child0Key, width: 100.0, height: 100.0),
- new Expanded(child: new Container(key: child1Key, width: 100.0, height: 100.0)),
- new Container(key: child2Key, width: 100.0, height: 100.0),
- ]
- )
+ new Container(key: child0Key, width: 100.0, height: 100.0, child: log(1)),
+ new Expanded(child: new Container(key: child1Key, width: 100.0, height: 100.0, child: log(2))),
+ new Container(key: child2Key, width: 100.0, height: 100.0, child: log(3)),
+ ],
+ ),
));
RenderBox renderBox;
@@ -51,9 +356,12 @@
expect(renderBox.size.height, equals(100.0));
boxParentData = renderBox.parentData;
expect(boxParentData.offset.dx, equals(700.0));
+
+ expect(OrderPainter.log, <int>[1, 2, 3]);
});
- testWidgets('Row with default main axis parameters', (WidgetTester tester) async {
+ testWidgets('Row with default main axis parameters - LTR', (WidgetTester tester) async {
+ OrderPainter.log.clear();
final Key rowKey = const Key('row');
final Key child0Key = const Key('child0');
final Key child1Key = const Key('child1');
@@ -65,12 +373,13 @@
await tester.pumpWidget(new Center(
child: new Row(
key: rowKey,
+ textDirection: TextDirection.ltr,
children: <Widget>[
- new Container(key: child0Key, width: 100.0, height: 100.0),
- new Container(key: child1Key, width: 100.0, height: 100.0),
- new Container(key: child2Key, width: 100.0, height: 100.0),
- ]
- )
+ new Container(key: child0Key, width: 100.0, height: 100.0, child: log(1)),
+ new Container(key: child1Key, width: 100.0, height: 100.0, child: log(2)),
+ new Container(key: child2Key, width: 100.0, height: 100.0, child: log(3)),
+ ],
+ ),
));
RenderBox renderBox;
@@ -97,9 +406,12 @@
expect(renderBox.size.height, equals(100.0));
boxParentData = renderBox.parentData;
expect(boxParentData.offset.dx, equals(200.0));
+
+ expect(OrderPainter.log, <int>[1, 2, 3]);
});
- testWidgets('Row with MainAxisAlignment.center', (WidgetTester tester) async {
+ testWidgets('Row with MainAxisAlignment.center - LTR', (WidgetTester tester) async {
+ OrderPainter.log.clear();
final Key rowKey = const Key('row');
final Key child0Key = const Key('child0');
final Key child1Key = const Key('child1');
@@ -110,11 +422,12 @@
child: new Row(
key: rowKey,
mainAxisAlignment: MainAxisAlignment.center,
+ textDirection: TextDirection.ltr,
children: <Widget>[
- new Container(key: child0Key, width: 100.0, height: 100.0),
- new Container(key: child1Key, width: 100.0, height: 100.0),
- ]
- )
+ new Container(key: child0Key, width: 100.0, height: 100.0, child: log(1)),
+ new Container(key: child1Key, width: 100.0, height: 100.0, child: log(2)),
+ ],
+ ),
));
RenderBox renderBox;
@@ -135,9 +448,12 @@
expect(renderBox.size.height, equals(100.0));
boxParentData = renderBox.parentData;
expect(boxParentData.offset.dx, equals(400.0));
+
+ expect(OrderPainter.log, <int>[1, 2]);
});
- testWidgets('Row with MainAxisAlignment.end', (WidgetTester tester) async {
+ testWidgets('Row with MainAxisAlignment.end - LTR', (WidgetTester tester) async {
+ OrderPainter.log.clear();
final Key rowKey = const Key('row');
final Key child0Key = const Key('child0');
final Key child1Key = const Key('child1');
@@ -148,13 +464,14 @@
await tester.pumpWidget(new Center(
child: new Row(
key: rowKey,
+ textDirection: TextDirection.ltr,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
- new Container(key: child0Key, width: 100.0, height: 100.0),
- new Container(key: child1Key, width: 100.0, height: 100.0),
- new Container(key: child2Key, width: 100.0, height: 100.0),
- ]
- )
+ new Container(key: child0Key, width: 100.0, height: 100.0, child: log(1)),
+ new Container(key: child1Key, width: 100.0, height: 100.0, child: log(2)),
+ new Container(key: child2Key, width: 100.0, height: 100.0, child: log(3)),
+ ],
+ ),
));
RenderBox renderBox;
@@ -181,9 +498,12 @@
expect(renderBox.size.height, equals(100.0));
boxParentData = renderBox.parentData;
expect(boxParentData.offset.dx, equals(700.0));
+
+ expect(OrderPainter.log, <int>[1, 2, 3]);
});
- testWidgets('Row with MainAxisAlignment.spaceBetween', (WidgetTester tester) async {
+ testWidgets('Row with MainAxisAlignment.spaceBetween - LTR', (WidgetTester tester) async {
+ OrderPainter.log.clear();
final Key rowKey = const Key('row');
final Key child0Key = const Key('child0');
final Key child1Key = const Key('child1');
@@ -195,12 +515,13 @@
child: new Row(
key: rowKey,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ textDirection: TextDirection.ltr,
children: <Widget>[
- new Container(key: child0Key, width: 100.0, height: 100.0),
- new Container(key: child1Key, width: 100.0, height: 100.0),
- new Container(key: child2Key, width: 100.0, height: 100.0),
- ]
- )
+ new Container(key: child0Key, width: 100.0, height: 100.0, child: log(1)),
+ new Container(key: child1Key, width: 100.0, height: 100.0, child: log(2)),
+ new Container(key: child2Key, width: 100.0, height: 100.0, child: log(3)),
+ ],
+ ),
));
RenderBox renderBox;
@@ -227,9 +548,12 @@
expect(renderBox.size.height, equals(100.0));
boxParentData = renderBox.parentData;
expect(boxParentData.offset.dx, equals(700.0));
+
+ expect(OrderPainter.log, <int>[1, 2, 3]);
});
- testWidgets('Row with MainAxisAlignment.spaceAround', (WidgetTester tester) async {
+ testWidgets('Row with MainAxisAlignment.spaceAround - LTR', (WidgetTester tester) async {
+ OrderPainter.log.clear();
final Key rowKey = const Key('row');
final Key child0Key = const Key('child0');
final Key child1Key = const Key('child1');
@@ -242,13 +566,14 @@
child: new Row(
key: rowKey,
mainAxisAlignment: MainAxisAlignment.spaceAround,
+ textDirection: TextDirection.ltr,
children: <Widget>[
- new Container(key: child0Key, width: 100.0, height: 100.0),
- new Container(key: child1Key, width: 100.0, height: 100.0),
- new Container(key: child2Key, width: 100.0, height: 100.0),
- new Container(key: child3Key, width: 100.0, height: 100.0),
- ]
- )
+ new Container(key: child0Key, width: 100.0, height: 100.0, child: log(1)),
+ new Container(key: child1Key, width: 100.0, height: 100.0, child: log(2)),
+ new Container(key: child2Key, width: 100.0, height: 100.0, child: log(3)),
+ new Container(key: child3Key, width: 100.0, height: 100.0, child: log(4)),
+ ],
+ ),
));
RenderBox renderBox;
@@ -281,9 +606,12 @@
expect(renderBox.size.height, equals(100.0));
boxParentData = renderBox.parentData;
expect(boxParentData.offset.dx, equals(650.0));
+
+ expect(OrderPainter.log, <int>[1, 2, 3, 4]);
});
- testWidgets('Row with MainAxisAlignment.spaceEvenly', (WidgetTester tester) async {
+ testWidgets('Row with MainAxisAlignment.spaceEvenly - LTR', (WidgetTester tester) async {
+ OrderPainter.log.clear();
final Key rowKey = const Key('row');
final Key child0Key = const Key('child0');
final Key child1Key = const Key('child1');
@@ -295,12 +623,13 @@
child: new Row(
key: rowKey,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ textDirection: TextDirection.ltr,
children: <Widget>[
- new Container(key: child0Key, width: 200.0, height: 100.0),
- new Container(key: child1Key, width: 200.0, height: 100.0),
- new Container(key: child2Key, width: 200.0, height: 100.0),
- ]
- )
+ new Container(key: child0Key, width: 200.0, height: 100.0, child: log(1)),
+ new Container(key: child1Key, width: 200.0, height: 100.0, child: log(2)),
+ new Container(key: child2Key, width: 200.0, height: 100.0, child: log(3)),
+ ],
+ ),
));
RenderBox renderBox;
@@ -327,9 +656,12 @@
expect(renderBox.size.height, equals(100.0));
boxParentData = renderBox.parentData;
expect(boxParentData.offset.dx, equals(550.0));
+
+ expect(OrderPainter.log, <int>[1, 2, 3]);
});
- testWidgets('Row and MainAxisSize.min', (WidgetTester tester) async {
+ testWidgets('Row and MainAxisSize.min - LTR', (WidgetTester tester) async {
+ OrderPainter.log.clear();
final Key rowKey = const Key('rowKey');
final Key child0Key = const Key('child0');
final Key child1Key = const Key('child1');
@@ -340,11 +672,12 @@
child: new Row(
key: rowKey,
mainAxisSize: MainAxisSize.min,
+ textDirection: TextDirection.ltr,
children: <Widget>[
- new Container(key: child0Key, width: 100.0, height: 100.0),
- new Container(key: child1Key, width: 150.0, height: 100.0)
- ]
- )
+ new Container(key: child0Key, width: 100.0, height: 100.0, child: log(1)),
+ new Container(key: child1Key, width: 150.0, height: 100.0, child: log(2)),
+ ],
+ ),
));
RenderBox renderBox;
@@ -365,26 +698,454 @@
expect(renderBox.size.height, equals(100.0));
boxParentData = renderBox.parentData;
expect(boxParentData.offset.dx, equals(100.0));
+
+ expect(OrderPainter.log, <int>[1, 2]);
});
- testWidgets('Row MainAxisSize.min layout at zero size', (WidgetTester tester) async {
+ testWidgets('Row MainAxisSize.min layout at zero size - LTR', (WidgetTester tester) async {
+ OrderPainter.log.clear();
final Key childKey = const Key('childKey');
await tester.pumpWidget(new Center(
child: new Container(
width: 0.0,
height: 0.0,
- child: new Row(
+ child: new Row(
+ textDirection: TextDirection.ltr,
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Container(
key: childKey,
width: 100.0,
- height: 100.0
- )
+ height: 100.0,
+ ),
],
- mainAxisSize: MainAxisSize.min
- )
- )
+ ),
+ ),
+ ));
+
+ final RenderBox renderBox = tester.renderObject(find.byKey(childKey));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(0.0));
+ });
+
+
+ // RTL
+
+ testWidgets('Row with one Flexible child - RTL', (WidgetTester tester) async {
+ OrderPainter.log.clear();
+ final Key rowKey = const Key('row');
+ final Key child0Key = const Key('child0');
+ final Key child1Key = const Key('child1');
+ final Key child2Key = const Key('child2');
+
+ // Default is MainAxisSize.max so the Row should be as wide as the test: 800.
+ // Default is MainAxisAlignment.start so children so the children's
+ // right edges should be at 0, 100, 700 from the right, child2's width should be 600
+ await tester.pumpWidget(new Center(
+ child: new Row(
+ key: rowKey,
+ textDirection: TextDirection.rtl,
+ children: <Widget>[
+ new Container(key: child0Key, width: 100.0, height: 100.0, child: log(1)),
+ new Expanded(child: new Container(key: child1Key, width: 100.0, height: 100.0, child: log(2))),
+ new Container(key: child2Key, width: 100.0, height: 100.0, child: log(3)),
+ ],
+ ),
+ ));
+
+ RenderBox renderBox;
+ BoxParentData boxParentData;
+
+ renderBox = tester.renderObject(find.byKey(rowKey));
+ expect(renderBox.size.width, equals(800.0));
+ expect(renderBox.size.height, equals(100.0));
+
+ renderBox = tester.renderObject(find.byKey(child0Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dx, equals(700.0));
+
+ renderBox = tester.renderObject(find.byKey(child1Key));
+ expect(renderBox.size.width, equals(600.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dx, equals(100.0));
+
+ renderBox = tester.renderObject(find.byKey(child2Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dx, equals(0.0));
+
+ expect(OrderPainter.log, <int>[1, 2, 3]);
+ });
+
+ testWidgets('Row with default main axis parameters - RTL', (WidgetTester tester) async {
+ OrderPainter.log.clear();
+ final Key rowKey = const Key('row');
+ final Key child0Key = const Key('child0');
+ final Key child1Key = const Key('child1');
+ final Key child2Key = const Key('child2');
+
+ // Default is MainAxisSize.max so the Row should be as wide as the test: 800.
+ // Default is MainAxisAlignment.start so children so the children's
+ // right edges should be at 0, 100, 200 from the right
+ await tester.pumpWidget(new Center(
+ child: new Row(
+ key: rowKey,
+ textDirection: TextDirection.rtl,
+ children: <Widget>[
+ new Container(key: child0Key, width: 100.0, height: 100.0, child: log(1)),
+ new Container(key: child1Key, width: 100.0, height: 100.0, child: log(2)),
+ new Container(key: child2Key, width: 100.0, height: 100.0, child: log(3)),
+ ],
+ ),
+ ));
+
+ RenderBox renderBox;
+ BoxParentData boxParentData;
+
+ renderBox = tester.renderObject(find.byKey(rowKey));
+ expect(renderBox.size.width, equals(800.0));
+ expect(renderBox.size.height, equals(100.0));
+
+ renderBox = tester.renderObject(find.byKey(child0Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dx, equals(700.0));
+
+ renderBox = tester.renderObject(find.byKey(child1Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dx, equals(600.0));
+
+ renderBox = tester.renderObject(find.byKey(child2Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dx, equals(500.0));
+
+ expect(OrderPainter.log, <int>[1, 2, 3]);
+ });
+
+ testWidgets('Row with MainAxisAlignment.center - RTL', (WidgetTester tester) async {
+ OrderPainter.log.clear();
+ final Key rowKey = const Key('row');
+ final Key child0Key = const Key('child0');
+ final Key child1Key = const Key('child1');
+
+ // Default is MainAxisSize.max so the Row should be as wide as the test: 800.
+ // The 100x100 children's right edges should be at 300, 400 from the right
+ await tester.pumpWidget(new Center(
+ child: new Row(
+ key: rowKey,
+ mainAxisAlignment: MainAxisAlignment.center,
+ textDirection: TextDirection.rtl,
+ children: <Widget>[
+ new Container(key: child0Key, width: 100.0, height: 100.0, child: log(1)),
+ new Container(key: child1Key, width: 100.0, height: 100.0, child: log(2)),
+ ],
+ ),
+ ));
+
+ RenderBox renderBox;
+ BoxParentData boxParentData;
+
+ renderBox = tester.renderObject(find.byKey(rowKey));
+ expect(renderBox.size.width, equals(800.0));
+ expect(renderBox.size.height, equals(100.0));
+
+ renderBox = tester.renderObject(find.byKey(child0Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dx, equals(400.0));
+
+ renderBox = tester.renderObject(find.byKey(child1Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dx, equals(300.0));
+
+ expect(OrderPainter.log, <int>[1, 2]);
+ });
+
+ testWidgets('Row with MainAxisAlignment.end - RTL', (WidgetTester tester) async {
+ OrderPainter.log.clear();
+ final Key rowKey = const Key('row');
+ final Key child0Key = const Key('child0');
+ final Key child1Key = const Key('child1');
+ final Key child2Key = const Key('child2');
+
+ // Default is MainAxisSize.max so the Row should be as wide as the test: 800.
+ // The 100x100 children's right edges should be at 500, 600, 700 from the right.
+ await tester.pumpWidget(new Center(
+ child: new Row(
+ key: rowKey,
+ textDirection: TextDirection.rtl,
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: <Widget>[
+ new Container(key: child0Key, width: 100.0, height: 100.0, child: log(1)),
+ new Container(key: child1Key, width: 100.0, height: 100.0, child: log(2)),
+ new Container(key: child2Key, width: 100.0, height: 100.0, child: log(3)),
+ ],
+ ),
+ ));
+
+ RenderBox renderBox;
+ BoxParentData boxParentData;
+
+ renderBox = tester.renderObject(find.byKey(rowKey));
+ expect(renderBox.size.width, equals(800.0));
+ expect(renderBox.size.height, equals(100.0));
+
+ renderBox = tester.renderObject(find.byKey(child0Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dx, equals(200.0));
+
+ renderBox = tester.renderObject(find.byKey(child1Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dx, equals(100.0));
+
+ renderBox = tester.renderObject(find.byKey(child2Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dx, equals(0.0));
+
+ expect(OrderPainter.log, <int>[1, 2, 3]);
+ });
+
+ testWidgets('Row with MainAxisAlignment.spaceBetween - RTL', (WidgetTester tester) async {
+ OrderPainter.log.clear();
+ final Key rowKey = const Key('row');
+ final Key child0Key = const Key('child0');
+ final Key child1Key = const Key('child1');
+ final Key child2Key = const Key('child2');
+
+ // Default is MainAxisSize.max so the Row should be as wide as the test: 800.
+ // The 100x100 children's right edges should be at 0, 350, 700 from the right
+ await tester.pumpWidget(new Center(
+ child: new Row(
+ key: rowKey,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ textDirection: TextDirection.rtl,
+ children: <Widget>[
+ new Container(key: child0Key, width: 100.0, height: 100.0, child: log(1)),
+ new Container(key: child1Key, width: 100.0, height: 100.0, child: log(2)),
+ new Container(key: child2Key, width: 100.0, height: 100.0, child: log(3)),
+ ],
+ ),
+ ));
+
+ RenderBox renderBox;
+ BoxParentData boxParentData;
+
+ renderBox = tester.renderObject(find.byKey(rowKey));
+ expect(renderBox.size.width, equals(800.0));
+ expect(renderBox.size.height, equals(100.0));
+
+ renderBox = tester.renderObject(find.byKey(child0Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dx, equals(700.0));
+
+ renderBox = tester.renderObject(find.byKey(child1Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dx, equals(350.0));
+
+ renderBox = tester.renderObject(find.byKey(child2Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dx, equals(0.0));
+
+ expect(OrderPainter.log, <int>[1, 2, 3]);
+ });
+
+ testWidgets('Row with MainAxisAlignment.spaceAround - RTL', (WidgetTester tester) async {
+ OrderPainter.log.clear();
+ final Key rowKey = const Key('row');
+ final Key child0Key = const Key('child0');
+ final Key child1Key = const Key('child1');
+ final Key child2Key = const Key('child2');
+ final Key child3Key = const Key('child3');
+
+ // Default is MainAxisSize.max so the Row should be as wide as the test: 800.
+ // The 100x100 children's right edges should be at 50, 250, 450, 650 from the right
+ await tester.pumpWidget(new Center(
+ child: new Row(
+ key: rowKey,
+ mainAxisAlignment: MainAxisAlignment.spaceAround,
+ textDirection: TextDirection.rtl,
+ children: <Widget>[
+ new Container(key: child0Key, width: 100.0, height: 100.0, child: log(1)),
+ new Container(key: child1Key, width: 100.0, height: 100.0, child: log(2)),
+ new Container(key: child2Key, width: 100.0, height: 100.0, child: log(3)),
+ new Container(key: child3Key, width: 100.0, height: 100.0, child: log(4)),
+ ],
+ ),
+ ));
+
+ RenderBox renderBox;
+ BoxParentData boxParentData;
+
+ renderBox = tester.renderObject(find.byKey(rowKey));
+ expect(renderBox.size.width, equals(800.0));
+ expect(renderBox.size.height, equals(100.0));
+
+ renderBox = tester.renderObject(find.byKey(child0Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dx, equals(650.0));
+
+ renderBox = tester.renderObject(find.byKey(child1Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dx, equals(450.0));
+
+ renderBox = tester.renderObject(find.byKey(child2Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dx, equals(250.0));
+
+ renderBox = tester.renderObject(find.byKey(child3Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dx, equals(50.0));
+
+ expect(OrderPainter.log, <int>[1, 2, 3, 4]);
+ });
+
+ testWidgets('Row with MainAxisAlignment.spaceEvenly - RTL', (WidgetTester tester) async {
+ OrderPainter.log.clear();
+ final Key rowKey = const Key('row');
+ final Key child0Key = const Key('child0');
+ final Key child1Key = const Key('child1');
+ final Key child2Key = const Key('child2');
+
+ // Default is MainAxisSize.max so the Row should be as wide as the test: 800.
+ // The 200x100 children's right edges should be at 50, 300, 550 from the right
+ await tester.pumpWidget(new Center(
+ child: new Row(
+ key: rowKey,
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ textDirection: TextDirection.rtl,
+ children: <Widget>[
+ new Container(key: child0Key, width: 200.0, height: 100.0, child: log(1)),
+ new Container(key: child1Key, width: 200.0, height: 100.0, child: log(2)),
+ new Container(key: child2Key, width: 200.0, height: 100.0, child: log(3)),
+ ],
+ ),
+ ));
+
+ RenderBox renderBox;
+ BoxParentData boxParentData;
+
+ renderBox = tester.renderObject(find.byKey(rowKey));
+ expect(renderBox.size.width, equals(800.0));
+ expect(renderBox.size.height, equals(100.0));
+
+ renderBox = tester.renderObject(find.byKey(child0Key));
+ expect(renderBox.size.width, equals(200.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dx, equals(550.0));
+
+ renderBox = tester.renderObject(find.byKey(child1Key));
+ expect(renderBox.size.width, equals(200.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dx, equals(300.0));
+
+ renderBox = tester.renderObject(find.byKey(child2Key));
+ expect(renderBox.size.width, equals(200.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dx, equals(50.0));
+
+ expect(OrderPainter.log, <int>[1, 2, 3]);
+ });
+
+ testWidgets('Row and MainAxisSize.min - RTL', (WidgetTester tester) async {
+ OrderPainter.log.clear();
+ final Key rowKey = const Key('rowKey');
+ final Key child0Key = const Key('child0');
+ final Key child1Key = const Key('child1');
+
+ // Row with MainAxisSize.min without flexible children shrink wraps.
+ // Row's width should be 250, children should be at 0, 100 from right.
+ await tester.pumpWidget(new Center(
+ child: new Row(
+ key: rowKey,
+ mainAxisSize: MainAxisSize.min,
+ textDirection: TextDirection.rtl,
+ children: <Widget>[
+ new Container(key: child0Key, width: 100.0, height: 100.0, child: log(1)),
+ new Container(key: child1Key, width: 150.0, height: 100.0, child: log(2)),
+ ],
+ ),
+ ));
+
+ RenderBox renderBox;
+ BoxParentData boxParentData;
+
+ renderBox = tester.renderObject(find.byKey(rowKey));
+ expect(renderBox.size.width, equals(250.0));
+ expect(renderBox.size.height, equals(100.0));
+
+ renderBox = tester.renderObject(find.byKey(child0Key));
+ expect(renderBox.size.width, equals(100.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dx, equals(150.0));
+
+ renderBox = tester.renderObject(find.byKey(child1Key));
+ expect(renderBox.size.width, equals(150.0));
+ expect(renderBox.size.height, equals(100.0));
+ boxParentData = renderBox.parentData;
+ expect(boxParentData.offset.dx, equals(0.0));
+
+ expect(OrderPainter.log, <int>[1, 2]);
+ });
+
+ testWidgets('Row MainAxisSize.min layout at zero size - RTL', (WidgetTester tester) async {
+ OrderPainter.log.clear();
+ final Key childKey = const Key('childKey');
+
+ await tester.pumpWidget(new Center(
+ child: new Container(
+ width: 0.0,
+ height: 0.0,
+ child: new Row(
+ textDirection: TextDirection.rtl,
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[
+ new Container(
+ key: childKey,
+ width: 100.0,
+ height: 100.0,
+ ),
+ ],
+ ),
+ ),
));
final RenderBox renderBox = tester.renderObject(find.byKey(childKey));
diff --git a/packages/flutter/test/widgets/rtl_test.dart b/packages/flutter/test/widgets/rtl_test.dart
new file mode 100644
index 0000000..2ab136d
--- /dev/null
+++ b/packages/flutter/test/widgets/rtl_test.dart
@@ -0,0 +1,70 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter/widgets.dart';
+
+void main() {
+ testWidgets('Padding RTL', (WidgetTester tester) async {
+ final Widget child = new Padding(
+ padding: new EdgeInsetsDirectional.only(start: 10.0),
+ child: new Placeholder(),
+ );
+ await tester.pumpWidget(new Directionality(
+ textDirection: TextDirection.ltr,
+ child: child,
+ ));
+ expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(10.0, 0.0));
+ await tester.pumpWidget(new Directionality(
+ textDirection: TextDirection.rtl,
+ child: child,
+ ));
+ expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(0.0, 0.0));
+ });
+
+ testWidgets('Container padding/margin RTL', (WidgetTester tester) async {
+ final Widget child = new Container(
+ padding: new EdgeInsetsDirectional.only(start: 6.0),
+ margin: new EdgeInsetsDirectional.only(end: 20.0, start: 4.0),
+ child: new Placeholder(),
+ );
+ await tester.pumpWidget(new Directionality(
+ textDirection: TextDirection.ltr,
+ child: child,
+ ));
+ expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(10.0, 0.0));
+ expect(tester.getTopRight(find.byType(Placeholder)), const Offset(780.0, 0.0));
+ await tester.pumpWidget(new Directionality(
+ textDirection: TextDirection.rtl,
+ child: child,
+ ));
+ expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(20.0, 0.0));
+ expect(tester.getTopRight(find.byType(Placeholder)), const Offset(790.0, 0.0));
+ });
+
+ testWidgets('Container padding/margin mixed RTL/absolute', (WidgetTester tester) async {
+ final Widget child = new Container(
+ padding: new EdgeInsets.only(left: 6.0),
+ margin: new EdgeInsetsDirectional.only(end: 20.0, start: 4.0),
+ child: new Placeholder(),
+ );
+ await tester.pumpWidget(new Directionality(
+ textDirection: TextDirection.ltr,
+ child: child,
+ ));
+ expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(10.0, 0.0));
+ expect(tester.getTopRight(find.byType(Placeholder)), const Offset(780.0, 0.0));
+ await tester.pumpWidget(new Directionality(
+ textDirection: TextDirection.rtl,
+ child: child,
+ ));
+ expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(26.0, 0.0));
+ expect(tester.getTopRight(find.byType(Placeholder)), const Offset(796.0, 0.0));
+ });
+
+ testWidgets('EdgeInsetsDirectional without Directionality', (WidgetTester tester) async {
+ await tester.pumpWidget(new Padding(padding: new EdgeInsetsDirectional.only()));
+ expect(tester.takeException(), isAssertionError);
+ });
+}
diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart
index 70042ad..094c747 100644
--- a/packages/flutter_test/lib/src/binding.dart
+++ b/packages/flutter_test/lib/src/binding.dart
@@ -433,7 +433,7 @@
}
));
assert(_parentZone != null);
- assert(_pendingExceptionDetails != null);
+ assert(_pendingExceptionDetails != null, 'A test overrode FlutterError.onError but either failed to return it to its original state, or had unexpected additional errors that it could not handle. Typically, this is caused by using expect() before restoring FlutterError.onError.');
_parentZone.run<Null>(_testCompletionHandler);
}
);
diff --git a/packages/flutter_test/test/widget_tester_test.dart b/packages/flutter_test/test/widget_tester_test.dart
index db5a578..e900de8 100644
--- a/packages/flutter_test/test/widget_tester_test.dart
+++ b/packages/flutter_test/test/widget_tester_test.dart
@@ -140,33 +140,42 @@
group('find.descendant', () {
testWidgets('finds one descendant', (WidgetTester tester) async {
- await tester.pumpWidget(new Row(children: <Widget>[
- new Column(children: <Text>[const Text('foo'), const Text('bar')])
- ]));
+ await tester.pumpWidget(new Row(
+ textDirection: TextDirection.ltr,
+ children: <Widget>[
+ new Column(children: <Text>[const Text('foo'), const Text('bar')]),
+ ],
+ ));
expect(find.descendant(
of: find.widgetWithText(Row, 'foo'),
- matching: find.text('bar')
+ matching: find.text('bar'),
), findsOneWidget);
});
testWidgets('finds two descendants with different ancestors', (WidgetTester tester) async {
- await tester.pumpWidget(new Row(children: <Widget>[
- new Column(children: <Text>[const Text('foo'), const Text('bar')]),
- new Column(children: <Text>[const Text('foo'), const Text('bar')]),
- ]));
+ await tester.pumpWidget(new Row(
+ textDirection: TextDirection.ltr,
+ children: <Widget>[
+ new Column(children: <Text>[const Text('foo'), const Text('bar')]),
+ new Column(children: <Text>[const Text('foo'), const Text('bar')]),
+ ],
+ ));
expect(find.descendant(
of: find.widgetWithText(Column, 'foo'),
- matching: find.text('bar')
+ matching: find.text('bar'),
), findsNWidgets(2));
});
testWidgets('fails with a descriptive message', (WidgetTester tester) async {
- await tester.pumpWidget(new Row(children: <Widget>[
- new Column(children: <Text>[const Text('foo')]),
- const Text('bar')
- ]));
+ await tester.pumpWidget(new Row(
+ textDirection: TextDirection.ltr,
+ children: <Widget>[
+ new Column(children: <Text>[const Text('foo')]),
+ const Text('bar'),
+ ],
+ ));
TestFailure failure;
try {
@@ -186,9 +195,12 @@
});
testWidgets('Root not matched by default', (WidgetTester tester) async {
- await tester.pumpWidget(new Row(children: <Widget>[
- new Column(children: <Text>[const Text('foo'), const Text('bar')])
- ]));
+ await tester.pumpWidget(new Row(
+ textDirection: TextDirection.ltr,
+ children: <Widget>[
+ new Column(children: <Text>[const Text('foo'), const Text('bar')]),
+ ],
+ ));
expect(find.descendant(
of: find.widgetWithText(Row, 'foo'),
@@ -197,9 +209,12 @@
});
testWidgets('Match the root', (WidgetTester tester) async {
- await tester.pumpWidget(new Row(children: <Widget>[
- new Column(children: <Text>[const Text('foo'), const Text('bar')])
- ]));
+ await tester.pumpWidget(new Row(
+ textDirection: TextDirection.ltr,
+ children: <Widget>[
+ new Column(children: <Text>[const Text('foo'), const Text('bar')]),
+ ],
+ ));
expect(find.descendant(
of: find.widgetWithText(Row, 'foo'),