diff --git a/packages/flutter/lib/src/painting/edge_insets.dart b/packages/flutter/lib/src/painting/edge_insets.dart index 1a8514afc2..c89edd9c86 100644 --- a/packages/flutter/lib/src/painting/edge_insets.dart +++ b/packages/flutter/lib/src/painting/edge_insets.dart @@ -752,10 +752,10 @@ class EdgeInsetsDirectional extends EdgeInsetsGeometry { 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); + case TextDirection.ltr: + return new EdgeInsets.fromLTRB(start, top, end, bottom); } return null; } @@ -856,10 +856,10 @@ class _MixedEdgeInsets extends EdgeInsetsGeometry { EdgeInsets resolve(TextDirection direction) { assert(direction != null); switch (direction) { + case TextDirection.rtl: + return new EdgeInsets.fromLTRB(_end + _left, _top, _start + _right, _bottom); case TextDirection.ltr: return new EdgeInsets.fromLTRB(_start + _left, _top, _end + _right, _bottom); - case TextDirection.rtl: - return new EdgeInsets.fromLTRB(_end + _left, _top, _start + _left, _bottom); } return null; } diff --git a/packages/flutter/lib/src/painting/fractional_offset.dart b/packages/flutter/lib/src/painting/fractional_offset.dart index 76c140d2ee..61c2bbf78d 100644 --- a/packages/flutter/lib/src/painting/fractional_offset.dart +++ b/packages/flutter/lib/src/painting/fractional_offset.dart @@ -21,8 +21,12 @@ abstract class FractionalOffsetGeometry { /// const constructors so that they can be used in const expressions. const FractionalOffsetGeometry(); - double get _dx; - double get _start; + /// The [FractionalOffset.dx] to which this object will [resolve] in [TextDirection.ltr]. + double get _dxForRTL; + + /// The [FractionalOffset.dx] to which this object will [resolve] in [TextDirection.ltr]. + double get _dxForLTR; + double get _dy; /// Returns the difference between two [FractionalOffsetGeometry] objects. @@ -42,9 +46,9 @@ abstract class FractionalOffsetGeometry { /// 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, + return new _SchrodingersFractionalOffset( + _dxForRTL - other._dxForRTL, + _dxForLTR - other._dxForLTR, _dy - other._dy, ); } @@ -61,9 +65,9 @@ abstract class FractionalOffsetGeometry { /// 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, + return new _SchrodingersFractionalOffset( + _dxForRTL + other._dxForRTL, + _dxForLTR + other._dxForLTR, _dy + other._dy, ); } @@ -113,22 +117,22 @@ abstract class FractionalOffsetGeometry { 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), + return new _SchrodingersFractionalOffset( + ui.lerpDouble(0.5, b._dxForRTL, t), + ui.lerpDouble(0.5, b._dxForLTR, 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), + return new _SchrodingersFractionalOffset( + ui.lerpDouble(a._dxForRTL, 0.5, t), + ui.lerpDouble(a._dxForLTR, 0.5, 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), + return new _SchrodingersFractionalOffset( + ui.lerpDouble(a._dxForRTL, b._dxForRTL, t), + ui.lerpDouble(a._dxForLTR, b._dxForLTR, t), ui.lerpDouble(a._dy, b._dy, t), ); } @@ -142,62 +146,15 @@ abstract class FractionalOffsetGeometry { /// * [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; + FractionalOffset resolve(TextDirection direction) { + assert(direction != null); + switch (direction) { + case TextDirection.rtl: + return new FractionalOffset(_dxForRTL, _dy); + case TextDirection.ltr: + return new FractionalOffset(_dxForLTR, _dy); } - 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)'; + return null; } @override @@ -205,13 +162,13 @@ abstract class FractionalOffsetGeometry { if (other is! FractionalOffsetGeometry) return false; final FractionalOffsetGeometry typedOther = other; - return _dx == typedOther._dx && - _start == typedOther._start && + return _dxForRTL == typedOther._dxForRTL && + _dxForLTR == typedOther._dxForLTR && _dy == typedOther._dy; } @override - int get hashCode => hashValues(_dx, _start, _dy); + int get hashCode => hashValues(_dxForRTL, _dxForLTR, _dy); } /// An offset that's expressed as a fraction of a [Size]. @@ -279,7 +236,10 @@ class FractionalOffset extends FractionalOffsetGeometry { final double dx; @override - double get _dx => dx; + double get _dxForRTL => dx; + + @override + double get _dxForLTR => dx; /// The distance fraction in the vertical direction. /// @@ -292,9 +252,6 @@ class FractionalOffset extends FractionalOffsetGeometry { @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); @@ -420,6 +377,32 @@ class FractionalOffset extends FractionalOffsetGeometry { @override FractionalOffset resolve(TextDirection direction) => this; + + static String _stringify(double dx, double dy) { + if (dx == 0.0 && dy == 0.0) + return 'FractionalOffset.topLeft'; + if (dx == 0.5 && dy == 0.0) + return 'FractionalOffset.topCenter'; + if (dx == 1.0 && dy == 0.0) + return 'FractionalOffset.topRight'; + if (dx == 0.0 && dy == 0.5) + return 'FractionalOffset.centerLeft'; + if (dx == 0.5 && dy == 0.5) + return 'FractionalOffset.center'; + if (dx == 1.0 && dy == 0.5) + return 'FractionalOffset.centerRight'; + if (dx == 0.0 && dy == 1.0) + return 'FractionalOffset.bottomLeft'; + if (dx == 0.5 && dy == 1.0) + return 'FractionalOffset.bottomCenter'; + if (dx == 1.0 && dy == 1.0) + return 'FractionalOffset.bottomRight'; + return 'FractionalOffset(${dx.toStringAsFixed(1)}, ' + '${dy.toStringAsFixed(1)})'; + } + + @override + String toString() => _stringify(dx, dy); } /// An offset that's expressed as a fraction of a [Size], but whose horizontal @@ -455,7 +438,10 @@ class FractionalOffsetDirectional extends FractionalOffsetGeometry { final double start; @override - double get _start => start; + double get _dxForRTL => 1.0 - start; + + @override + double get _dxForLTR => start; /// The distance fraction in the vertical direction. /// @@ -471,9 +457,6 @@ class FractionalOffsetDirectional extends FractionalOffsetGeometry { @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); @@ -578,84 +561,89 @@ class FractionalOffsetDirectional extends FractionalOffsetGeometry { } @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; + String toString() { + 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)})'; } } -class _MixedFractionalOffset extends FractionalOffsetGeometry { - const _MixedFractionalOffset(this._dx, this._start, this._dy); +class _SchrodingersFractionalOffset extends FractionalOffsetGeometry { + const _SchrodingersFractionalOffset(this._dxForRTL, this._dxForLTR, this._dy); @override - final double _dx; + final double _dxForRTL; @override - final double _start; + final double _dxForLTR; @override final double _dy; @override - _MixedFractionalOffset operator -() { - return new _MixedFractionalOffset( - -_dx, - -_start, + _SchrodingersFractionalOffset operator -() { + return new _SchrodingersFractionalOffset( + -_dxForRTL, + -_dxForLTR, -_dy, ); } @override - _MixedFractionalOffset operator *(double other) { - return new _MixedFractionalOffset( - _dx * other, - _start * other, + _SchrodingersFractionalOffset operator *(double other) { + return new _SchrodingersFractionalOffset( + _dxForRTL * other, + _dxForLTR * other, _dy * other, ); } @override - _MixedFractionalOffset operator /(double other) { - return new _MixedFractionalOffset( - _dx / other, - _start / other, + _SchrodingersFractionalOffset operator /(double other) { + return new _SchrodingersFractionalOffset( + _dxForRTL / other, + _dxForLTR / other, _dy / other, ); } @override - _MixedFractionalOffset operator ~/(double other) { - return new _MixedFractionalOffset( - (_dx ~/ other).toDouble(), - (_start ~/ other).toDouble(), + _SchrodingersFractionalOffset operator ~/(double other) { + return new _SchrodingersFractionalOffset( + (_dxForRTL ~/ other).toDouble(), + (_dxForLTR ~/ other).toDouble(), (_dy ~/ other).toDouble(), ); } @override - _MixedFractionalOffset operator %(double other) { - return new _MixedFractionalOffset( - _dx % other, - _start % other, + _SchrodingersFractionalOffset operator %(double other) { + return new _SchrodingersFractionalOffset( + _dxForRTL % other, + _dxForLTR % 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; + String toString() { + if (_dxForRTL == _dxForLTR) + return FractionalOffset._stringify(_dxForRTL, _dy); + + return '${FractionalOffset._stringify(_dxForRTL, _dy)} in RTL' + ' or ' + '${FractionalOffset._stringify(_dxForLTR, _dy)} in LTR'; } } diff --git a/packages/flutter/lib/src/rendering/tweens.dart b/packages/flutter/lib/src/rendering/tweens.dart index 87ee32d87b..7bc031a4b0 100644 --- a/packages/flutter/lib/src/rendering/tweens.dart +++ b/packages/flutter/lib/src/rendering/tweens.dart @@ -11,11 +11,16 @@ import 'package:flutter/painting.dart'; /// appropriate for rectangles. /// /// See [Tween] for a discussion on how to use interpolation objects. +/// +/// See also: +/// +/// * [FractionalOffsetGeometryTween], which interpolates between two +/// [FractionalOffsetGeometry] objects. class FractionalOffsetTween extends Tween { /// Creates a fractional offset tween. /// /// The [begin] and [end] properties may be null; the null value - /// is treated as meaning the top left corner. + /// is treated as meaning the center. FractionalOffsetTween({ FractionalOffset begin, FractionalOffset end }) : super(begin: begin, end: end); @@ -23,3 +28,29 @@ class FractionalOffsetTween extends Tween { @override FractionalOffset lerp(double t) => FractionalOffset.lerp(begin, end, t); } + +/// An interpolation between two [FractionalOffsetGeometry]. +/// +/// This class specializes the interpolation of [Tween] +/// to be appropriate for rectangles. +/// +/// See [Tween] for a discussion on how to use interpolation objects. +/// +/// See also: +/// +/// * [FractionalOffsetTween], which interpolates between two +/// [FractionalOffset] objects. +class FractionalOffsetGeometryTween extends Tween { + /// Creates a fractional offset geometry tween. + /// + /// The [begin] and [end] properties may be null; the null value + /// is treated as meaning the center. + FractionalOffsetGeometryTween({ + FractionalOffsetGeometry begin, + FractionalOffsetGeometry end, + }) : super(begin: begin, end: end); + + /// Returns the value this variable has at the given animation clock value. + @override + FractionalOffsetGeometry lerp(double t) => FractionalOffsetGeometry.lerp(begin, end, t); +} diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 505379ae6c..2090de2a3b 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -29,6 +29,7 @@ export 'package:flutter/rendering.dart' show FlowDelegate, FlowPaintingContext, FractionalOffsetTween, + FractionalOffsetGeometryTween, HitTestBehavior, LayerLink, MainAxisAlignment, diff --git a/packages/flutter/lib/src/widgets/implicit_animations.dart b/packages/flutter/lib/src/widgets/implicit_animations.dart index 1a50ae47a2..c9335d8f64 100644 --- a/packages/flutter/lib/src/widgets/implicit_animations.dart +++ b/packages/flutter/lib/src/widgets/implicit_animations.dart @@ -61,6 +61,11 @@ class DecorationTween extends Tween { /// [EdgeInsets.lerp]. /// /// See [Tween] for a discussion on how to use interpolation objects. +/// +/// See also: +/// +/// * [EdgeInsetsGeometryTween], which interpolates between two +/// [EdgeInsetsGeometry] objects. class EdgeInsetsTween extends Tween { /// Creates an [EdgeInsets] tween. /// @@ -73,6 +78,28 @@ class EdgeInsetsTween extends Tween { EdgeInsets lerp(double t) => EdgeInsets.lerp(begin, end, t); } +/// An interpolation between two [EdgeInsetsGeometry]s. +/// +/// This class specializes the interpolation of [Tween] to +/// use [EdgeInsetsGeometry.lerp]. +/// +/// See [Tween] for a discussion on how to use interpolation objects. +/// +/// See also: +/// +/// * [EdgeInsetsTween], which interpolates between two [EdgeInsets] objects. +class EdgeInsetsGeometryTween extends Tween { + /// Creates an [EdgeInsetsGeometry] tween. + /// + /// The [begin] and [end] properties may be null; the null value + /// is treated as an [EdgeInsetsGeometry] with no inset. + EdgeInsetsGeometryTween({ EdgeInsetsGeometry begin, EdgeInsetsGeometry end }) : super(begin: begin, end: end); + + /// Returns the value this variable has at the given animation clock value. + @override + EdgeInsetsGeometry lerp(double t) => EdgeInsetsGeometry.lerp(begin, end, t); +} + /// An interpolation between two [BorderRadius]s. /// /// This class specializes the interpolation of [Tween] to use @@ -358,11 +385,11 @@ class AnimatedContainer extends ImplicitlyAnimatedWidget { /// constraints are unbounded, then the child will be shrink-wrapped instead. /// /// Ignored if [child] is null. - final FractionalOffset alignment; + final FractionalOffsetGeometry alignment; /// 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]. /// @@ -383,7 +410,7 @@ class AnimatedContainer extends ImplicitlyAnimatedWidget { 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; @@ -394,33 +421,33 @@ class AnimatedContainer extends ImplicitlyAnimatedWidget { @override void debugFillProperties(DiagnosticPropertiesBuilder description) { super.debugFillProperties(description); - description.add(new DiagnosticsProperty('alignment', alignment, showName: false, defaultValue: null)); - description.add(new DiagnosticsProperty('padding', padding, defaultValue: null)); + description.add(new DiagnosticsProperty('alignment', alignment, showName: false, defaultValue: null)); + description.add(new DiagnosticsProperty('padding', padding, defaultValue: null)); description.add(new DiagnosticsProperty('bg', decoration, defaultValue: null)); description.add(new DiagnosticsProperty('fg', foregroundDecoration, defaultValue: null)); description.add(new DiagnosticsProperty('constraints', constraints, defaultValue: null, showName: false)); - description.add(new DiagnosticsProperty('margin', margin, defaultValue: null)); + description.add(new DiagnosticsProperty('margin', margin, defaultValue: null)); description.add(new ObjectFlagProperty.has('transform', transform)); } } class _AnimatedContainerState extends AnimatedWidgetBaseState { - FractionalOffsetTween _alignment; - EdgeInsetsTween _padding; + FractionalOffsetGeometryTween _alignment; + EdgeInsetsGeometryTween _padding; DecorationTween _decoration; DecorationTween _foregroundDecoration; BoxConstraintsTween _constraints; - EdgeInsetsTween _margin; + EdgeInsetsGeometryTween _margin; Matrix4Tween _transform; @override void forEachTween(TweenVisitor visitor) { - _alignment = visitor(_alignment, widget.alignment, (dynamic value) => new FractionalOffsetTween(begin: value)); - _padding = visitor(_padding, widget.padding, (dynamic value) => new EdgeInsetsTween(begin: value)); + _alignment = visitor(_alignment, widget.alignment, (dynamic value) => new FractionalOffsetGeometryTween(begin: value)); + _padding = visitor(_padding, widget.padding, (dynamic value) => new EdgeInsetsGeometryTween(begin: value)); _decoration = visitor(_decoration, widget.decoration, (dynamic value) => new DecorationTween(begin: value)); _foregroundDecoration = visitor(_foregroundDecoration, widget.foregroundDecoration, (dynamic value) => new DecorationTween(begin: value)); _constraints = visitor(_constraints, widget.constraints, (dynamic value) => new BoxConstraintsTween(begin: value)); - _margin = visitor(_margin, widget.margin, (dynamic value) => new EdgeInsetsTween(begin: value)); + _margin = visitor(_margin, widget.margin, (dynamic value) => new EdgeInsetsGeometryTween(begin: value)); _transform = visitor(_transform, widget.transform, (dynamic value) => new Matrix4Tween(begin: value)); } @@ -441,12 +468,12 @@ class _AnimatedContainerState extends AnimatedWidgetBaseState @override void debugFillProperties(DiagnosticPropertiesBuilder description) { super.debugFillProperties(description); - description.add(new DiagnosticsProperty('alignment', _alignment, showName: false, defaultValue: null)); - description.add(new DiagnosticsProperty('padding', _padding, defaultValue: null)); + description.add(new DiagnosticsProperty('alignment', _alignment, showName: false, defaultValue: null)); + description.add(new DiagnosticsProperty('padding', _padding, defaultValue: null)); description.add(new DiagnosticsProperty('bg', _decoration, defaultValue: null)); description.add(new DiagnosticsProperty('fg', _foregroundDecoration, defaultValue: null)); description.add(new DiagnosticsProperty('constraints', _constraints, showName: false, defaultValue: null)); - description.add(new DiagnosticsProperty('margin', _margin, defaultValue: null)); + description.add(new DiagnosticsProperty('margin', _margin, defaultValue: null)); description.add(new ObjectFlagProperty.has('transform', _transform)); } } diff --git a/packages/flutter/test/painting/fractional_offset_test.dart b/packages/flutter/test/painting/fractional_offset_test.dart index 15ce3a6d54..a91fac34f2 100644 --- a/packages/flutter/test/painting/fractional_offset_test.dart +++ b/packages/flutter/test/painting/fractional_offset_test.dart @@ -73,15 +73,15 @@ void main() { 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, 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), new FractionalOffset(lerpDouble(0.875, 2.0, 0.5), 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(normal1, mixed1, 0.5).resolve(TextDirection.rtl), new FractionalOffset(lerpDouble(0.25, 0.0625 + 0.8125, 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); @@ -98,6 +98,35 @@ void main() { expect(FractionalOffsetGeometry.lerp(mixed1, mixed2, 0.25), mixed3); }); + test('lerp commutes with resolve', () { + final List offsets = [ + const FractionalOffset(-1.0, 0.65), + const FractionalOffsetDirectional(-1.0, 0.45), + const FractionalOffsetDirectional(0.125, 0.625), + const FractionalOffset(0.25, 0.875), + const FractionalOffset(0.0625, 0.5625).add(const FractionalOffsetDirectional(0.1875, 0.6875)), + const FractionalOffsetDirectional(2.0, 3.0), + const FractionalOffset(2.0, 3.0), + const FractionalOffset(2.0, 3.0).add(const FractionalOffsetDirectional(5.0, 3.0)), + const FractionalOffset(10.0, 20.0).add(const FractionalOffsetDirectional(30.0, 50.0)), + const FractionalOffset(70.0, 110.0).add(const FractionalOffsetDirectional(130.0, 170.0)), + const FractionalOffset(25.0, 42.5).add(const FractionalOffsetDirectional(55.0, 80.0)), + ]; + + final List times = [ 0.0, 0.25, 0.5, 0.75, 1.0 ]; + + for (TextDirection direction in TextDirection.values) { + for (FractionalOffsetGeometry a in offsets) { + for (FractionalOffsetGeometry b in offsets) { + for (double t in times) { + expect(FractionalOffsetGeometry.lerp(a, b, t).resolve(direction), + FractionalOffset.lerp(a.resolve(direction), b.resolve(direction), t)); + } + } + } + } + }); + test('FractionalOffsetGeometry add/subtract', () { final FractionalOffsetGeometry directional = const FractionalOffsetDirectional(1.0, 2.0); final FractionalOffsetGeometry normal = const FractionalOffset(3.0, 5.0); @@ -136,10 +165,10 @@ void main() { test('FractionalOffsetGeometry toString', () { expect(const FractionalOffset(1.0001, 2.0001).toString(), 'FractionalOffset(1.0, 2.0)'); expect(const FractionalOffset(0.0, 0.0).toString(), 'FractionalOffset.topLeft'); - expect(const FractionalOffset(0.0, 1.0).add(const FractionalOffsetDirectional(1.0, 0.0)).toString(), 'FractionalOffsetDirectional.bottomEnd'); + expect(const FractionalOffset(0.0, 1.0).add(const FractionalOffsetDirectional(1.0, 0.0)).toString(), 'FractionalOffset.bottomLeft in RTL or FractionalOffset.bottomRight in LTR'); expect(const FractionalOffset(0.0001, 0.0001).toString(), 'FractionalOffset(0.0, 0.0)'); expect(const FractionalOffset(0.0, 0.0).toString(), 'FractionalOffset.topLeft'); expect(const FractionalOffsetDirectional(0.0, 0.0).toString(), 'FractionalOffsetDirectional.topStart'); - expect(const FractionalOffset(1.0, 1.0).add(const FractionalOffsetDirectional(1.0, 1.0)).toString(), 'FractionalOffset(1.0, 2.0) + FractionalOffsetDirectional(1.0, 0.0)'); + expect(const FractionalOffset(1.0, 1.0).add(const FractionalOffsetDirectional(1.0, 1.0)).toString(), 'FractionalOffset(1.0, 2.0) in RTL or FractionalOffset(2.0, 2.0) in LTR'); }); } diff --git a/packages/flutter/test/widgets/animated_container_test.dart b/packages/flutter/test/widgets/animated_container_test.dart index 8003926ca2..dcd89cd53f 100644 --- a/packages/flutter/test/widgets/animated_container_test.dart +++ b/packages/flutter/test/widgets/animated_container_test.dart @@ -139,6 +139,90 @@ void main() { expect(tester.binding.transientCallbackCount, 0); }); + testWidgets('AnimatedContainer padding visual-to-directional animation', (WidgetTester tester) async { + final Key target = new UniqueKey(); + + await tester.pumpWidget( + new Directionality( + textDirection: TextDirection.rtl, + child: new AnimatedContainer( + duration: const Duration(milliseconds: 200), + padding: const EdgeInsets.only(right: 50.0), + child: new SizedBox.expand(key: target), + ), + ), + ); + + expect(tester.getSize(find.byKey(target)), const Size(750.0, 600.0)); + expect(tester.getTopRight(find.byKey(target)), const Offset(750.0, 0.0)); + + await tester.pumpWidget( + new Directionality( + textDirection: TextDirection.rtl, + child: new AnimatedContainer( + duration: const Duration(milliseconds: 200), + padding: const EdgeInsetsDirectional.only(start: 100.0), + child: new SizedBox.expand(key: target), + ), + ), + ); + + expect(tester.getSize(find.byKey(target)), const Size(750.0, 600.0)); + expect(tester.getTopRight(find.byKey(target)), const Offset(750.0, 0.0)); + + await tester.pump(const Duration(milliseconds: 100)); + + expect(tester.getSize(find.byKey(target)), const Size(725.0, 600.0)); + expect(tester.getTopRight(find.byKey(target)), const Offset(725.0, 0.0)); + + await tester.pump(const Duration(milliseconds: 500)); + + expect(tester.getSize(find.byKey(target)), const Size(700.0, 600.0)); + expect(tester.getTopRight(find.byKey(target)), const Offset(700.0, 0.0)); + }); + + testWidgets('AnimatedContainer alignment visual-to-directional animation', (WidgetTester tester) async { + final Key target = new UniqueKey(); + + await tester.pumpWidget( + new Directionality( + textDirection: TextDirection.rtl, + child: new AnimatedContainer( + duration: const Duration(milliseconds: 200), + alignment: FractionalOffset.topRight, + child: new SizedBox(key: target, width: 100.0, height: 200.0), + ), + ), + ); + + expect(tester.getSize(find.byKey(target)), const Size(100.0, 200.0)); + expect(tester.getTopRight(find.byKey(target)), const Offset(800.0, 0.0)); + + await tester.pumpWidget( + new Directionality( + textDirection: TextDirection.rtl, + child: new AnimatedContainer( + duration: const Duration(milliseconds: 200), + alignment: FractionalOffsetDirectional.bottomStart, + child: new SizedBox(key: target, width: 100.0, height: 200.0), + ), + ), + ); + + expect(tester.getSize(find.byKey(target)), const Size(100.0, 200.0)); + expect(tester.getTopRight(find.byKey(target)), const Offset(800.0, 0.0)); + + await tester.pump(const Duration(milliseconds: 100)); + + expect(tester.getSize(find.byKey(target)), const Size(100.0, 200.0)); + expect(tester.getTopRight(find.byKey(target)), const Offset(800.0, 200.0)); + + await tester.pump(const Duration(milliseconds: 500)); + + expect(tester.getSize(find.byKey(target)), const Size(100.0, 200.0)); + expect(tester.getTopRight(find.byKey(target)), const Offset(800.0, 400.0)); + }); + testWidgets('Animation rerun', (WidgetTester tester) async { await tester.pumpWidget( new Center(