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.
This commit is contained in:
Ian Hickson
2017-08-28 12:50:24 -07:00
committed by GitHub
parent 3dd1a05a8a
commit f235a2c104
59 changed files with 4926 additions and 1638 deletions

View File

@@ -9,7 +9,10 @@ import '../widgets/gestures.dart';
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;

View File

@@ -26,11 +26,14 @@ const TextStyle _errorTextStyle = const TextStyle(
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 @@ class _MaterialAppState extends State<MaterialApp> {
// 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 @@ class _MaterialAppState extends State<MaterialApp> {
onGenerateRoute: _onGenerateRoute,
onUnknownRoute: _onUnknownRoute,
locale: widget.locale,
localizationsDelegates: _createLocalizationsDelegates(),
localizationsDelegates: _localizationsDelegates,
showPerformanceOverlay: widget.showPerformanceOverlay,
checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,

View File

@@ -106,16 +106,16 @@ class Chip extends StatelessWidget {
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 @@ class Chip extends StatelessWidget {
));
if (deletable) {
rightPadding = 0.0;
endPadding = 0.0;
children.add(new GestureDetector(
onTap: Feedback.wrapForTap(onDeleted, context),
child: new Tooltip(
@@ -152,7 +152,7 @@ class Chip extends StatelessWidget {
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),

View File

@@ -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 @@ class MaterialLocalizations {
/// 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);
}
}

View File

@@ -150,6 +150,8 @@ class _TabLabelBarRenderer extends RenderFlex {
MainAxisSize mainAxisSize,
MainAxisAlignment mainAxisAlignment,
CrossAxisAlignment crossAxisAlignment,
TextDirection textDirection,
VerticalDirection verticalDirection,
TextBaseline textBaseline,
@required this.onPerformLayout,
}) : assert(onPerformLayout != null),
@@ -159,6 +161,8 @@ class _TabLabelBarRenderer extends RenderFlex {
mainAxisSize: mainAxisSize,
mainAxisAlignment: mainAxisAlignment,
crossAxisAlignment: crossAxisAlignment,
textDirection: textDirection,
verticalDirection: verticalDirection,
textBaseline: textBaseline,
);
@@ -188,6 +192,8 @@ class _TabLabelBar extends Flex {
Key key,
MainAxisAlignment mainAxisAlignment,
CrossAxisAlignment crossAxisAlignment,
TextDirection textDirection,
VerticalDirection verticalDirection: VerticalDirection.down,
List<Widget> children: const <Widget>[],
this.onPerformLayout,
}) : super(
@@ -197,6 +203,8 @@ class _TabLabelBar extends Flex {
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
textDirection: textDirection,
verticalDirection: verticalDirection,
);
final ValueChanged<List<double>> onPerformLayout;
@@ -208,6 +216,8 @@ class _TabLabelBar extends Flex {
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: mainAxisSize,
crossAxisAlignment: crossAxisAlignment,
textDirection: getEffectiveTextDirection(context),
verticalDirection: verticalDirection,
textBaseline: textBaseline,
onPerformLayout: onPerformLayout,
);

View File

@@ -90,3 +90,70 @@ enum RenderComparison {
/// 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,
}

View File

@@ -8,13 +8,255 @@ import 'package:flutter/foundation.dart';
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 @@ enum Axis {
///
/// 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 @@ class EdgeInsets {
/// 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 @@ class EdgeInsets {
/// 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 @@ class EdgeInsets {
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 @@ class EdgeInsets {
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;
}
}

View File

@@ -8,6 +8,212 @@ import 'package:flutter/foundation.dart';
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 @@ import 'basic_types.dart';
///
/// * [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 @@ class FractionalOffset {
/// 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 @@ class FractionalOffset {
/// 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 @@ class FractionalOffset {
/// 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 @@ class FractionalOffset {
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;
}
}

View File

@@ -2149,6 +2149,11 @@ abstract class RenderBoxContainerDefaultsMixin<ChildType extends RenderBox, Pare
///
/// 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 @@ abstract class RenderBoxContainerDefaultsMixin<ChildType extends RenderBox, Pare
}
/// 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) {

View File

@@ -100,9 +100,21 @@ enum MainAxisSize {
/// * [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 @@ enum CrossAxisAlignment {
/// 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 @@ enum CrossAxisAlignment {
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 @@ typedef double _ChildSizingFunction(RenderBox child, double extent);
/// [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 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
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 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
_mainAxisAlignment = mainAxisAlignment,
_mainAxisSize = mainAxisSize,
_crossAxisAlignment = crossAxisAlignment,
_textDirection = textDirection,
_verticalDirection = verticalDirection,
_textBaseline = textBaseline {
addAll(children);
}
@@ -234,6 +299,14 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
}
/// 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 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
}
/// 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 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
}
}
/// 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 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
}
}
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 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
@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 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
// 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 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
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 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
}
// 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 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
}
break;
}
if (flipMainAxis)
childMainPosition -= _getMainSize(child);
switch (_direction) {
case Axis.horizontal:
childParentData.offset = new Offset(childMainPosition, childCrossPosition);
@@ -730,7 +919,11 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
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 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
@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));
}
}

View File

@@ -43,7 +43,7 @@ class AbstractNode {
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].

View File

@@ -91,29 +91,58 @@ class RenderPadding extends RenderShiftedBox {
///
/// 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 @@ class RenderPadding extends RenderShiftedBox {
@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 @@ class RenderPadding extends RenderShiftedBox {
@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 @@ class RenderPadding extends RenderShiftedBox {
@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 @@ class RenderPadding extends RenderShiftedBox {
@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 @@ class RenderPadding extends RenderShiftedBox {
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 @@ class RenderPadding extends RenderShiftedBox {
@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));
}
}

View File

@@ -94,6 +94,10 @@ Axis axisDirectionToAxis(AxisDirection axisDirection) {
/// 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) {

View File

@@ -23,6 +23,19 @@ import 'widget_inspector.dart';
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 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
}
}
// Combine the Localizations for Widgets with the ones contributed
// by the localizationsDelegates parameter, if any.
Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates sync* {
yield const _WidgetsLocalizationsDelegate(); // TODO(ianh): make this configurable
if (widget.localizationsDelegates != null)
yield* widget.localizationsDelegates;
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) { }
@@ -281,7 +302,7 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
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,

View File

@@ -1116,20 +1116,27 @@ class Padding extends SingleChildRenderObjectWidget {
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 @@ class SliverPadding extends SingleChildRenderObjectWidget {
/// 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 @@ class Flex extends MultiChildRenderObjectWidget {
///
/// 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 @@ class Flex extends MultiChildRenderObjectWidget {
/// 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 @@ class Flex extends MultiChildRenderObjectWidget {
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: mainAxisSize,
crossAxisAlignment: crossAxisAlignment,
textBaseline: textBaseline
textDirection: getEffectiveTextDirection(context),
verticalDirection: verticalDirection,
textBaseline: textBaseline,
);
}
@@ -2567,8 +2666,22 @@ class Flex extends MultiChildRenderObjectWidget {
..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 Flex extends MultiChildRenderObjectWidget {
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 @@ class Row extends Flex {
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: mainAxisSize,
crossAxisAlignment: crossAxisAlignment,
textBaseline: textBaseline
textDirection: textDirection,
verticalDirection: verticalDirection,
textBaseline: textBaseline,
);
}
@@ -2843,14 +2968,22 @@ class Row extends Flex {
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 @@ class Column extends Flex {
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: mainAxisSize,
crossAxisAlignment: crossAxisAlignment,
textBaseline: textBaseline
textDirection: textDirection,
verticalDirection: verticalDirection,
textBaseline: textBaseline,
);
}

View File

@@ -282,7 +282,7 @@ class Container extends StatelessWidget {
/// 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 @@ class Container extends StatelessWidget {
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 @@ class Container extends StatelessWidget {
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 @@ class Container extends StatelessWidget {
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));
}
}

View File

@@ -7,6 +7,7 @@ import 'dart:ui' show Locale;
import 'package:flutter/foundation.dart';
import 'basic.dart';
import 'binding.dart';
import 'container.dart';
import 'framework.dart';
@@ -90,6 +91,47 @@ abstract class LocalizationsDelegate<T> {
/// 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 @@ class _LocalizationsScope extends InheritedWidget {
///
/// 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 @@ class _LocalizationsScope extends InheritedWidget {
/// 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 @@ class Localizations extends StatefulWidget {
@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 @@ class _LocalizationsState extends State<Localizations> {
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,
),
);
}
}

View File

@@ -264,7 +264,7 @@ class ScrollController extends ChangeNotifier {
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');
}

View File

@@ -7,10 +7,19 @@ import 'package:flutter_test/flutter_test.dart';
import '../services/mocks_for_image_cache.dart';
Future<Null> pumpWidgetWithBoilerplate(WidgetTester tester, Widget widget) async {
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: widget,
),
);
}
void main() {
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 @@ void main() {
],
));
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 @@ void main() {
});
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 @@ void main() {
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 @@ void main() {
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)),

View File

@@ -67,7 +67,10 @@ void main() {
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);
});

View File

@@ -8,31 +8,34 @@ import 'package:flutter/rendering.dart';
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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'),
),
),
),
);

View File

@@ -6,7 +6,12 @@ import 'package:flutter/material.dart';
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(),
),
);
});
}

View File

@@ -107,29 +107,31 @@ void main() {
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 @@ void main() {
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 @@ void main() {
);
});
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 @@ void main() {
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));
});
}

View File

@@ -10,10 +10,17 @@ import 'package:flutter/rendering.dart';
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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(

View File

@@ -11,22 +11,24 @@ void main() {
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 @@ void main() {
// 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));

View File

@@ -10,23 +10,25 @@ void main() {
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),
),
child: new DecoratedBox(
decoration: new BoxDecoration(
color: Colors.green[500],
),
),
footer: new GridTileBar(
key: footerKey,
title: const Text('Footer'),
backgroundColor: Colors.black38,
),
),
footer: new GridTileBar(
key: footerKey,
title: const Text('Footer'),
backgroundColor: Colors.black38,
),
));

View File

@@ -98,15 +98,18 @@ void main() {
'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),
),
],
),
),
),
);

View File

@@ -9,37 +9,43 @@ void main() {
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),
),
),
));

View File

@@ -9,32 +9,35 @@ const Color kSelectedColor = const Color(0xFF00FF00);
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')),
],
),
),
],
),
),
),
),

View File

@@ -10,29 +10,31 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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);

View File

@@ -14,6 +14,15 @@ import '../rendering/mock_canvas.dart';
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 @@ Widget buildFrame({
bool isScrollable: false,
Color indicatorColor,
}) {
return new Material(
return boilerplate(
child: new DefaultTabController(
initialIndex: tabs.indexOf(value),
length: tabs.length,
@@ -253,7 +262,7 @@ void main() {
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 @@ void main() {
Color secondColor;
await tester.pumpWidget(
new Material(
boilerplate(
child: new TabBar(
controller: controller,
labelColor: Colors.green[500],
@@ -667,7 +676,7 @@ void main() {
);
await tester.pumpWidget(
new Material(
boilerplate(
child: new TabBarView(
controller: controller,
children: <Widget>[ const Text('First'), const Text('Second') ],
@@ -765,7 +774,7 @@ void main() {
);
Widget buildFrame() {
return new Material(
return boilerplate(
child: new TabBar(
key: new UniqueKey(),
controller: controller,
@@ -893,7 +902,7 @@ void main() {
);
await tester.pumpWidget(
new Material(
boilerplate(
child: new TabBar(
isScrollable: true,
controller: controller,
@@ -927,7 +936,7 @@ void main() {
);
await tester.pumpWidget(
new Material(
boilerplate(
child: new Column(
children: <Widget>[
new TabBar(
@@ -985,7 +994,7 @@ void main() {
);
await tester.pumpWidget(
new Material(
boilerplate(
child: new Semantics(
container: true,
child: new TabBar(
@@ -1034,7 +1043,7 @@ void main() {
);
await tester.pumpWidget(
new Material(
boilerplate(
child: new Column(
children: <Widget>[
new TabBar(
@@ -1074,7 +1083,7 @@ void main() {
);
await tester.pumpWidget(
new Material(
boilerplate(
child: new Column(
children: <Widget>[
new TabBar(
@@ -1129,7 +1138,7 @@ void main() {
);
await tester.pumpWidget(
new Material(
boilerplate(
child: new Column(
children: <Widget>[
new TabBar(

File diff suppressed because it is too large Load Diff

View File

@@ -167,7 +167,7 @@ void main() {
],
).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)',
),
);
});

View File

@@ -50,4 +50,141 @@ void main() {
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)');
});
}

View File

@@ -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 @@ void main() {
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 @@ void main() {
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)');
});
}

View File

@@ -11,13 +11,13 @@ import 'rendering_tester.dart';
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 @@ void main() {
);
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 @@ void main() {
);
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 @@ void main() {
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 @@ void main() {
' mainAxisAlignment: start\n'
' mainAxisSize: max\n'
' crossAxisAlignment: center\n'
' textBaseline: null\n'
' verticalDirection: down\n'
),
);
});
@@ -103,7 +106,7 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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));
});
}

View File

@@ -36,7 +36,7 @@ void main() {
' │ 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 @@ void main() {
' │ 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 @@ void main() {
' │ 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'

View File

@@ -36,7 +36,7 @@ void main() {
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; }));

View File

@@ -196,32 +196,35 @@ void main() {
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 @@ void main() {
});
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 @@ void main() {
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 @@ void main() {
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);

View File

@@ -19,6 +19,7 @@ Widget buildFrame(ScrollPhysics physics) {
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()),

View File

@@ -7,6 +7,8 @@ import 'package:flutter/rendering.dart';
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 @@ void main() {
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));
});
}

View File

@@ -59,7 +59,8 @@ void main() {
' │ 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 @@ void main() {
' │ 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 @@ void main() {
' │ 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'

View File

@@ -47,6 +47,7 @@ void main() {
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 @@ void main() {
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')),

View File

@@ -145,6 +145,7 @@ void main() {
node: parentFocusScope,
autofocus: true,
child: new Row(
textDirection: TextDirection.ltr,
children: <Widget>[
const TestFocusable(
no: 'a',
@@ -193,6 +194,7 @@ void main() {
new FocusScope(
node: parentFocusScope,
child: new Row(
textDirection: TextDirection.ltr,
children: <Widget>[
const TestFocusable(
no: 'a',
@@ -218,6 +220,7 @@ void main() {
new FocusScope(
node: parentFocusScope,
child: new Row(
textDirection: TextDirection.ltr,
children: <Widget>[
const TestFocusable(
no: 'a',

View File

@@ -486,7 +486,7 @@ void main() {
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'

View File

@@ -24,10 +24,13 @@ void main() {
});
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 @@ void main() {
});
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 @@ void main() {
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();

View File

@@ -235,6 +235,7 @@ void main() {
// 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 @@ void main() {
await tester.pumpWidget(
new Row(
textDirection: TextDirection.ltr,
children: <Widget> [
new MediaQuery(
key: mediaQueryKey2,

View File

@@ -29,6 +29,7 @@ class SizeChangerState extends State<SizeChanger> {
@override
Widget build(BuildContext context) {
return new Row(
textDirection: TextDirection.ltr,
children: <Widget>[
new SizedBox(
height: _flag ? 50.0 : 100.0,

View File

@@ -25,38 +25,44 @@ void main() {
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();
})
)
),
],
));
});
}

View File

@@ -42,6 +42,9 @@ class SyncTestLocalizationsDelegate extends LocalizationsDelegate<TestLocalizati
shouldReloadValues.add(prefix != old.prefix);
return prefix != old.prefix;
}
@override
String toString() => '$runtimeType($prefix)';
}
class AsyncTestLocalizationsDelegate extends LocalizationsDelegate<TestLocalizations> {
@@ -58,6 +61,9 @@ class AsyncTestLocalizationsDelegate extends LocalizationsDelegate<TestLocalizat
shouldReloadValues.add(prefix != old.prefix);
return prefix != old.prefix;
}
@override
String toString() => '$runtimeType($prefix)';
}
class MoreLocalizations {
@@ -289,6 +295,7 @@ void main() {
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 @@ void main() {
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 @@ void main() {
});
}
// 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;
}

View File

@@ -23,71 +23,74 @@ class _CustomPhysics extends ClampingScrollPhysics {
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'),
),
],
),
],
),
),
),
),

View File

@@ -42,7 +42,7 @@ void main() {
.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',

View File

@@ -43,7 +43,7 @@ void main() {
' ╎ │ 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 @@ void main() {
' ╎ │ 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'

View File

@@ -335,6 +335,7 @@ void main() {
await tester.pumpWidget(
new Row(
textDirection: TextDirection.ltr,
children: <Widget>[
new StateMarker(
key: key1,
@@ -352,6 +353,7 @@ void main() {
await tester.pumpWidget(
new Row(
textDirection: TextDirection.ltr,
children: <Widget>[
new StateMarker(
key: key2,

View File

@@ -16,6 +16,7 @@ void main() {
key: rotatedBoxKey,
quarterTurns: 1,
child: new Row(
textDirection: TextDirection.ltr,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new GestureDetector(

File diff suppressed because it is too large Load Diff

View File

@@ -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);
});
}

View File

@@ -433,7 +433,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
}
));
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);
}
);

View File

@@ -140,33 +140,42 @@ void main() {
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 @@ void main() {
});
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 @@ void main() {
});
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'),