Image RTL (#12230)
This commit is contained in:
parent
ff45d50655
commit
e04bf3285d
@ -321,9 +321,20 @@ class _BoxDecorationPainter extends BoxPainter {
|
||||
ImageInfo _image;
|
||||
|
||||
void _paintBackgroundImage(Canvas canvas, Rect rect, ImageConfiguration configuration) {
|
||||
// TODO(ianh): factor this out into a DecorationImage.paint method.
|
||||
final DecorationImage backgroundImage = _decoration.image;
|
||||
if (backgroundImage == null)
|
||||
return;
|
||||
|
||||
bool flipHorizontally = false;
|
||||
if (backgroundImage.matchTextDirection) {
|
||||
// We check this first so that the assert will fire immediately, not just when the
|
||||
// image is ready.
|
||||
assert(configuration.textDirection != null, 'matchTextDirection can only be used when a TextDirection is available.');
|
||||
if (configuration.textDirection == TextDirection.rtl)
|
||||
flipHorizontally = true;
|
||||
}
|
||||
|
||||
final ImageStream newImageStream = backgroundImage.image.resolve(configuration);
|
||||
if (newImageStream.key != _imageStream?.key) {
|
||||
_imageStream?.removeListener(_imageListener);
|
||||
@ -350,9 +361,10 @@ class _BoxDecorationPainter extends BoxPainter {
|
||||
image: image,
|
||||
colorFilter: backgroundImage.colorFilter,
|
||||
fit: backgroundImage.fit,
|
||||
alignment: backgroundImage.alignment,
|
||||
alignment: backgroundImage.alignment.resolve(configuration.textDirection),
|
||||
centerSlice: backgroundImage.centerSlice,
|
||||
repeat: backgroundImage.repeat,
|
||||
flipHorizontally: flipHorizontally,
|
||||
);
|
||||
|
||||
if (clipPath != null)
|
||||
|
@ -35,15 +35,20 @@ enum ImageRepeat {
|
||||
class DecorationImage {
|
||||
/// Creates an image to show in a [BoxDecoration].
|
||||
///
|
||||
/// The [image] argument must not be null.
|
||||
/// The [image], [alignment], [repeat], and [matchTextDirection] arguments
|
||||
/// must not be null.
|
||||
const DecorationImage({
|
||||
@required this.image,
|
||||
this.colorFilter,
|
||||
this.fit,
|
||||
this.alignment,
|
||||
this.alignment: FractionalOffset.center,
|
||||
this.centerSlice,
|
||||
this.repeat: ImageRepeat.noRepeat,
|
||||
}) : assert(image != null);
|
||||
this.matchTextDirection: false,
|
||||
}) : assert(image != null),
|
||||
assert(alignment != null),
|
||||
assert(repeat != null),
|
||||
assert(matchTextDirection != null);
|
||||
|
||||
/// The image to be painted into the decoration.
|
||||
///
|
||||
@ -64,12 +69,23 @@ class DecorationImage {
|
||||
|
||||
/// How to align the image within its bounds.
|
||||
///
|
||||
/// An alignment of (0.0, 0.0) aligns the image to the top-left corner of its
|
||||
/// layout bounds. An alignment of (1.0, 0.5) aligns the image to the middle
|
||||
/// of the right edge of its layout bounds.
|
||||
/// The alignment aligns the given position in the image to the given position
|
||||
/// in the layout bounds. For example, a [FractionalOffset] alignment of (0.0,
|
||||
/// 0.0) aligns the image to the top-left corner of its layout bounds, while a
|
||||
/// [FractionalOffset] alignment of (1.0, 1.0) aligns the bottom right of the
|
||||
/// image with the bottom right corner of its layout bounds. Similarly, an
|
||||
/// alignment of (0.5, 1.0) aligns the bottom middle of the image with the
|
||||
/// middle of the bottom edge of its layout bounds.
|
||||
///
|
||||
/// To display a subpart of an image, consider using a [CustomPainter] and
|
||||
/// [Canvas.drawImageRect].
|
||||
///
|
||||
/// If the [alignment] is [TextDirection]-dependent (i.e. if it is a
|
||||
/// [FractionalOffsetDirectional]), then a [TextDirection] must be available
|
||||
/// when the image is painted.
|
||||
///
|
||||
/// Defaults to [FractionalOffset.center].
|
||||
final FractionalOffset alignment;
|
||||
final FractionalOffsetGeometry alignment;
|
||||
|
||||
/// The center slice for a nine-patch image.
|
||||
///
|
||||
@ -92,6 +108,15 @@ class DecorationImage {
|
||||
/// by the image.
|
||||
final ImageRepeat repeat;
|
||||
|
||||
/// Whether to paint the image in the direction of the [TextDirection].
|
||||
///
|
||||
/// If this is true, then in [TextDirection.ltr] contexts, the image will be
|
||||
/// drawn with its origin in the top left (the "normal" painting direction for
|
||||
/// images); and in [TextDirection.rtl] contexts, the image will be drawn with
|
||||
/// a scaling factor of -1 in the horizontal direction so that the origin is
|
||||
/// in the top right.
|
||||
final bool matchTextDirection;
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
if (identical(this, other))
|
||||
@ -104,11 +129,12 @@ class DecorationImage {
|
||||
&& fit == typedOther.fit
|
||||
&& alignment == typedOther.alignment
|
||||
&& centerSlice == typedOther.centerSlice
|
||||
&& repeat == typedOther.repeat;
|
||||
&& repeat == typedOther.repeat
|
||||
&& matchTextDirection == typedOther.matchTextDirection;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => hashValues(image, colorFilter, fit, alignment, centerSlice, repeat);
|
||||
int get hashCode => hashValues(image, colorFilter, fit, alignment, centerSlice, repeat, matchTextDirection);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
@ -120,33 +146,44 @@ class DecorationImage {
|
||||
!(fit == BoxFit.fill && centerSlice != null) &&
|
||||
!(fit == BoxFit.scaleDown && centerSlice == null))
|
||||
properties.add('$fit');
|
||||
if (alignment != null)
|
||||
properties.add('$alignment');
|
||||
properties.add('$alignment');
|
||||
if (centerSlice != null)
|
||||
properties.add('centerSlice: $centerSlice');
|
||||
if (repeat != ImageRepeat.noRepeat)
|
||||
properties.add('$repeat');
|
||||
if (matchTextDirection)
|
||||
properties.add('match text direction');
|
||||
return '$runtimeType(${properties.join(", ")})';
|
||||
}
|
||||
}
|
||||
|
||||
/// Paints an image into the given rectangle on the canvas.
|
||||
///
|
||||
/// The arguments have the following meanings:
|
||||
///
|
||||
/// * `canvas`: The canvas onto which the image will be painted.
|
||||
///
|
||||
/// * `rect`: The region of the canvas into which the image will be painted.
|
||||
/// The image might not fill the entire rectangle (e.g., depending on the
|
||||
/// `fit`). If `rect` is empty, nothing is painted.
|
||||
///
|
||||
/// * `image`: The image to paint onto the canvas.
|
||||
///
|
||||
/// * `colorFilter`: If non-null, the color filter to apply when painting the
|
||||
/// image.
|
||||
///
|
||||
/// * `fit`: How the image should be inscribed into `rect`. If null, the
|
||||
/// default behavior depends on `centerSlice`. If `centerSlice` is also null,
|
||||
/// the default behavior is [BoxFit.scaleDown]. If `centerSlice` is
|
||||
/// non-null, the default behavior is [BoxFit.fill]. See [BoxFit] for
|
||||
/// details.
|
||||
/// * `repeat`: If the image does not fill `rect`, whether and how the image
|
||||
/// should be repeated to fill `rect`. By default, the image is not repeated.
|
||||
/// See [ImageRepeat] for details.
|
||||
///
|
||||
/// * `alignment`: How the destination rectangle defined by applying `fit` is
|
||||
/// aligned within `rect`. For example, if `fit` is [BoxFit.contain] and
|
||||
/// `alignment` is [FractionalOffset.bottomRight], the image will be as large
|
||||
/// as possible within `rect` and placed with its bottom right corner at the
|
||||
/// bottom right corner of `rect`. Defaults to [FractionalOffset.center].
|
||||
///
|
||||
/// * `centerSlice`: The image is drawn in nine portions described by splitting
|
||||
/// the image by drawing two horizontal lines and two vertical lines, where
|
||||
/// `centerSlice` describes the rectangle formed by the four points where
|
||||
@ -157,11 +194,19 @@ class DecorationImage {
|
||||
/// remaining five regions are drawn by stretching them to fit such that they
|
||||
/// exactly cover the destination rectangle while maintaining their relative
|
||||
/// positions.
|
||||
/// * `alignment`: How the destination rectangle defined by applying `fit` is
|
||||
/// aligned within `rect`. For example, if `fit` is [BoxFit.contain] and
|
||||
/// `alignment` is [FractionalOffset.bottomRight], the image will be as large
|
||||
/// as possible within `rect` and placed with its bottom right corner at the
|
||||
/// bottom right corner of `rect`.
|
||||
///
|
||||
/// * `repeat`: If the image does not fill `rect`, whether and how the image
|
||||
/// should be repeated to fill `rect`. By default, the image is not repeated.
|
||||
/// See [ImageRepeat] for details.
|
||||
///
|
||||
/// * `flipHorizontally`: Whether to flip the image horizontally. This is
|
||||
/// occasionally used with images in right-to-left environments, for images
|
||||
/// that were designed for left-to-right locales (or vice versa). Be careful,
|
||||
/// when using this, to not flip images with integral shadows, text, or other
|
||||
/// effects that will look incorrect when flipped.
|
||||
///
|
||||
/// The `canvas`, `rect`, `image`, `alignment`, `repeat`, and `flipHorizontally`
|
||||
/// arguments must not be null.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
@ -174,12 +219,16 @@ void paintImage({
|
||||
@required ui.Image image,
|
||||
ColorFilter colorFilter,
|
||||
BoxFit fit,
|
||||
FractionalOffset alignment,
|
||||
FractionalOffset alignment: FractionalOffset.center,
|
||||
Rect centerSlice,
|
||||
ImageRepeat repeat: ImageRepeat.noRepeat,
|
||||
bool flipHorizontally: false,
|
||||
}) {
|
||||
assert(canvas != null);
|
||||
assert(image != null);
|
||||
assert(alignment != null);
|
||||
assert(repeat != null);
|
||||
assert(flipHorizontally != null);
|
||||
if (rect.isEmpty)
|
||||
return;
|
||||
Size outputSize = rect.size;
|
||||
@ -219,16 +268,23 @@ void paintImage({
|
||||
// to nearest-neighbor.
|
||||
paint.filterQuality = FilterQuality.low;
|
||||
}
|
||||
final double dx = (outputSize.width - destinationSize.width) * (alignment?.dx ?? 0.5);
|
||||
final double dy = (outputSize.height - destinationSize.height) * (alignment?.dy ?? 0.5);
|
||||
final double dx = (outputSize.width - destinationSize.width) * (flipHorizontally ? 1.0 - alignment.dx : alignment.dx);
|
||||
final double dy = (outputSize.height - destinationSize.height) * alignment.dy;
|
||||
final Offset destinationPosition = rect.topLeft.translate(dx, dy);
|
||||
final Rect destinationRect = destinationPosition & destinationSize;
|
||||
if (repeat != ImageRepeat.noRepeat) {
|
||||
final bool needSave = repeat != ImageRepeat.noRepeat || flipHorizontally;
|
||||
if (needSave)
|
||||
canvas.save();
|
||||
if (repeat != ImageRepeat.noRepeat)
|
||||
canvas.clipRect(rect);
|
||||
if (flipHorizontally) {
|
||||
final double dx = -(rect.left + rect.width / 2.0);
|
||||
canvas.translate(-dx, 0.0);
|
||||
canvas.scale(-1.0, 1.0);
|
||||
canvas.translate(dx, 0.0);
|
||||
}
|
||||
if (centerSlice == null) {
|
||||
final Rect sourceRect = (alignment ?? FractionalOffset.center).inscribe(
|
||||
final Rect sourceRect = alignment.inscribe(
|
||||
fittedSizes.source, Offset.zero & inputSize
|
||||
);
|
||||
for (Rect tileRect in _generateImageTileRects(rect, destinationRect, repeat))
|
||||
@ -237,7 +293,7 @@ void paintImage({
|
||||
for (Rect tileRect in _generateImageTileRects(rect, destinationRect, repeat))
|
||||
canvas.drawImageNine(image, centerSlice, tileRect, paint);
|
||||
}
|
||||
if (repeat != ImageRepeat.noRepeat)
|
||||
if (needSave)
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,10 @@ export 'package:flutter/painting.dart' show
|
||||
/// various fields on this class in more detail.
|
||||
class RenderImage extends RenderBox {
|
||||
/// Creates a render box that displays an image.
|
||||
///
|
||||
/// The [scale], [alignment], [repeat], and [matchTextDirection] arguments
|
||||
/// must not be null. The [textDirection] argument must not be null if
|
||||
/// [alignment] will need resolving or if [matchTextDirection] is true.
|
||||
RenderImage({
|
||||
ui.Image image,
|
||||
double width,
|
||||
@ -28,22 +32,46 @@ class RenderImage extends RenderBox {
|
||||
Color color,
|
||||
BlendMode colorBlendMode,
|
||||
BoxFit fit,
|
||||
FractionalOffset alignment,
|
||||
FractionalOffsetGeometry alignment: FractionalOffset.center,
|
||||
ImageRepeat repeat: ImageRepeat.noRepeat,
|
||||
Rect centerSlice
|
||||
}) : _image = image,
|
||||
_width = width,
|
||||
_height = height,
|
||||
_scale = scale,
|
||||
_color = color,
|
||||
_colorBlendMode = colorBlendMode,
|
||||
_fit = fit,
|
||||
_alignment = alignment,
|
||||
_repeat = repeat,
|
||||
_centerSlice = centerSlice {
|
||||
Rect centerSlice,
|
||||
bool matchTextDirection: false,
|
||||
TextDirection textDirection,
|
||||
}) : assert(scale != null),
|
||||
assert(repeat != null),
|
||||
assert(alignment != null),
|
||||
assert(matchTextDirection != null),
|
||||
_image = image,
|
||||
_width = width,
|
||||
_height = height,
|
||||
_scale = scale,
|
||||
_color = color,
|
||||
_colorBlendMode = colorBlendMode,
|
||||
_fit = fit,
|
||||
_alignment = alignment,
|
||||
_repeat = repeat,
|
||||
_centerSlice = centerSlice,
|
||||
_matchTextDirection = matchTextDirection,
|
||||
_textDirection = textDirection {
|
||||
_updateColorFilter();
|
||||
}
|
||||
|
||||
FractionalOffset _resolvedAlignment;
|
||||
bool _flipHorizontally;
|
||||
|
||||
void _resolve() {
|
||||
if (_resolvedAlignment != null)
|
||||
return;
|
||||
_resolvedAlignment = alignment.resolve(textDirection);
|
||||
_flipHorizontally = matchTextDirection && textDirection == TextDirection.rtl;
|
||||
}
|
||||
|
||||
void _markNeedResolution() {
|
||||
_resolvedAlignment = null;
|
||||
_flipHorizontally = null;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
/// The image to display.
|
||||
ui.Image get image => _image;
|
||||
ui.Image _image;
|
||||
@ -147,19 +175,24 @@ class RenderImage extends RenderBox {
|
||||
}
|
||||
|
||||
/// How to align the image within its bounds.
|
||||
FractionalOffset get alignment => _alignment;
|
||||
FractionalOffset _alignment;
|
||||
set alignment(FractionalOffset value) {
|
||||
///
|
||||
/// If this is set to a text-direction-dependent value, [textDirection] must
|
||||
/// not be null.
|
||||
FractionalOffsetGeometry get alignment => _alignment;
|
||||
FractionalOffsetGeometry _alignment;
|
||||
set alignment(FractionalOffsetGeometry value) {
|
||||
assert(value != null);
|
||||
if (value == _alignment)
|
||||
return;
|
||||
_alignment = value;
|
||||
markNeedsPaint();
|
||||
_markNeedResolution();
|
||||
}
|
||||
|
||||
/// How to repeat this image if it doesn't fill its layout bounds.
|
||||
ImageRepeat get repeat => _repeat;
|
||||
ImageRepeat _repeat;
|
||||
set repeat(ImageRepeat value) {
|
||||
assert(value != null);
|
||||
if (value == _repeat)
|
||||
return;
|
||||
_repeat = value;
|
||||
@ -182,6 +215,44 @@ class RenderImage extends RenderBox {
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
/// Whether to paint the image in the direction of the [TextDirection].
|
||||
///
|
||||
/// If this is true, then in [TextDirection.ltr] contexts, the image will be
|
||||
/// drawn with its origin in the top left (the "normal" painting direction for
|
||||
/// images); and in [TextDirection.rtl] contexts, the image will be drawn with
|
||||
/// a scaling factor of -1 in the horizontal direction so that the origin is
|
||||
/// in the top right.
|
||||
///
|
||||
/// This is occasionally used with images in right-to-left environments, for
|
||||
/// images that were designed for left-to-right locales. Be careful, when
|
||||
/// using this, to not flip images with integral shadows, text, or other
|
||||
/// effects that will look incorrect when flipped.
|
||||
///
|
||||
/// If this is set to true, [textDirection] must not be null.
|
||||
bool get matchTextDirection => _matchTextDirection;
|
||||
bool _matchTextDirection;
|
||||
set matchTextDirection(bool value) {
|
||||
assert(value != null);
|
||||
if (value == _matchTextDirection)
|
||||
return;
|
||||
_matchTextDirection = value;
|
||||
_markNeedResolution();
|
||||
}
|
||||
|
||||
/// The text direction with which to resolve [alignment].
|
||||
///
|
||||
/// This may be changed to null, but only after the [alignment] and
|
||||
/// [matchTextDirection] properties have been changed to values that do not
|
||||
/// depend on the direction.
|
||||
TextDirection get textDirection => _textDirection;
|
||||
TextDirection _textDirection;
|
||||
set textDirection(TextDirection value) {
|
||||
if (_textDirection == value)
|
||||
return;
|
||||
_textDirection = value;
|
||||
_markNeedResolution();
|
||||
}
|
||||
|
||||
/// Find a size for the render image within the given constraints.
|
||||
///
|
||||
/// - The dimensions of the RenderImage must fit within the constraints.
|
||||
@ -246,15 +317,19 @@ class RenderImage extends RenderBox {
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
if (_image == null)
|
||||
return;
|
||||
_resolve();
|
||||
assert(_resolvedAlignment != null);
|
||||
assert(_flipHorizontally != null);
|
||||
paintImage(
|
||||
canvas: context.canvas,
|
||||
rect: offset & size,
|
||||
image: _image,
|
||||
colorFilter: _colorFilter,
|
||||
fit: _fit,
|
||||
alignment: _alignment,
|
||||
alignment: _resolvedAlignment,
|
||||
centerSlice: _centerSlice,
|
||||
repeat: _repeat
|
||||
repeat: _repeat,
|
||||
flipHorizontally: _flipHorizontally,
|
||||
);
|
||||
}
|
||||
|
||||
@ -271,5 +346,7 @@ class RenderImage extends RenderBox {
|
||||
description.add(new DiagnosticsProperty<FractionalOffset>('alignment', alignment, defaultValue: null));
|
||||
description.add(new EnumProperty<ImageRepeat>('repeat', repeat, defaultValue: ImageRepeat.noRepeat));
|
||||
description.add(new DiagnosticsProperty<Rect>('centerSlice', centerSlice, defaultValue: null));
|
||||
description.add(new FlagProperty('matchTextDirection', value: matchTextDirection, ifTrue: 'match text direction'));
|
||||
description.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import 'layer.dart';
|
||||
import 'node.dart';
|
||||
import 'semantics.dart';
|
||||
|
||||
export 'package:flutter/foundation.dart' show FlutterError, InformationCollector, DiagnosticsNode, DiagnosticsProperty, StringProperty, DoubleProperty, EnumProperty, IntProperty, DiagnosticPropertiesBuilder;
|
||||
export 'package:flutter/foundation.dart' show FlutterError, InformationCollector, DiagnosticsNode, DiagnosticsProperty, StringProperty, DoubleProperty, EnumProperty, FlagProperty, IntProperty, DiagnosticPropertiesBuilder;
|
||||
export 'package:flutter/gestures.dart' show HitTestEntry, HitTestResult;
|
||||
export 'package:flutter/painting.dart';
|
||||
|
||||
|
@ -98,20 +98,20 @@ class RenderPadding extends RenderShiftedBox {
|
||||
assert(padding.isNonNegative),
|
||||
_textDirection = textDirection,
|
||||
_padding = padding,
|
||||
super(child) {
|
||||
_applyUpdate();
|
||||
}
|
||||
super(child);
|
||||
|
||||
// The resolved absolute insets.
|
||||
EdgeInsets _resolvedPadding;
|
||||
|
||||
void _applyUpdate() {
|
||||
final EdgeInsets resolvedPadding = padding.resolve(textDirection);
|
||||
assert(resolvedPadding.isNonNegative);
|
||||
if (_resolvedPadding != resolvedPadding) {
|
||||
_resolvedPadding = resolvedPadding;
|
||||
markNeedsLayout();
|
||||
}
|
||||
void _resolve() {
|
||||
if (_resolvedPadding != null)
|
||||
return;
|
||||
_resolvedPadding = padding.resolve(textDirection);
|
||||
assert(_resolvedPadding.isNonNegative);
|
||||
}
|
||||
|
||||
void _markNeedResolution() {
|
||||
_resolvedPadding = null;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
/// The amount to pad the child in each dimension.
|
||||
@ -126,21 +126,25 @@ class RenderPadding extends RenderShiftedBox {
|
||||
if (_padding == value)
|
||||
return;
|
||||
_padding = value;
|
||||
_applyUpdate();
|
||||
_markNeedResolution();
|
||||
}
|
||||
|
||||
/// The text direction with which to resolve [padding].
|
||||
///
|
||||
/// This may be changed to null, but only after the [padding] has been changed
|
||||
/// to a value that does not depend on the direction.
|
||||
TextDirection get textDirection => _textDirection;
|
||||
TextDirection _textDirection;
|
||||
set textDirection(TextDirection value) {
|
||||
if (_textDirection == value)
|
||||
return;
|
||||
_textDirection = value;
|
||||
_applyUpdate();
|
||||
_markNeedResolution();
|
||||
}
|
||||
|
||||
@override
|
||||
double computeMinIntrinsicWidth(double height) {
|
||||
_resolve();
|
||||
final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
|
||||
final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
|
||||
if (child != null) // next line relies on double.INFINITY absorption
|
||||
@ -150,6 +154,7 @@ class RenderPadding extends RenderShiftedBox {
|
||||
|
||||
@override
|
||||
double computeMaxIntrinsicWidth(double height) {
|
||||
_resolve();
|
||||
final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
|
||||
final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
|
||||
if (child != null) // next line relies on double.INFINITY absorption
|
||||
@ -159,6 +164,7 @@ class RenderPadding extends RenderShiftedBox {
|
||||
|
||||
@override
|
||||
double computeMinIntrinsicHeight(double width) {
|
||||
_resolve();
|
||||
final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
|
||||
final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
|
||||
if (child != null) // next line relies on double.INFINITY absorption
|
||||
@ -168,6 +174,7 @@ class RenderPadding extends RenderShiftedBox {
|
||||
|
||||
@override
|
||||
double computeMaxIntrinsicHeight(double width) {
|
||||
_resolve();
|
||||
final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
|
||||
final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
|
||||
if (child != null) // next line relies on double.INFINITY absorption
|
||||
@ -177,6 +184,7 @@ class RenderPadding extends RenderShiftedBox {
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
_resolve();
|
||||
assert(_resolvedPadding != null);
|
||||
if (child == null) {
|
||||
size = constraints.constrain(new Size(
|
||||
@ -226,19 +234,19 @@ abstract class RenderAligningShiftedBox extends RenderShiftedBox {
|
||||
}) : assert(alignment != null),
|
||||
_alignment = alignment,
|
||||
_textDirection = textDirection,
|
||||
super(child) {
|
||||
_applyUpdate();
|
||||
}
|
||||
super(child);
|
||||
|
||||
// The resolved absolute alignment.
|
||||
FractionalOffset _resolvedAlignment;
|
||||
|
||||
void _applyUpdate() {
|
||||
final FractionalOffset resolvedAlignment = alignment.resolve(textDirection);
|
||||
if (_resolvedAlignment != resolvedAlignment) {
|
||||
_resolvedAlignment = resolvedAlignment;
|
||||
markNeedsLayout();
|
||||
}
|
||||
void _resolve() {
|
||||
if (_resolvedAlignment != null)
|
||||
return;
|
||||
_resolvedAlignment = alignment.resolve(textDirection);
|
||||
}
|
||||
|
||||
void _markNeedResolution() {
|
||||
_resolvedAlignment = null;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
/// How to align the child.
|
||||
@ -251,7 +259,7 @@ abstract class RenderAligningShiftedBox extends RenderShiftedBox {
|
||||
/// For example, a value of 0.5 means that the center of the child is aligned
|
||||
/// with the center of the parent.
|
||||
///
|
||||
/// If this is set to an [FractionalOffsetDirectional] object, then
|
||||
/// If this is set to a [FractionalOffsetDirectional] object, then
|
||||
/// [textDirection] must not be null.
|
||||
FractionalOffsetGeometry get alignment => _alignment;
|
||||
FractionalOffsetGeometry _alignment;
|
||||
@ -263,17 +271,20 @@ abstract class RenderAligningShiftedBox extends RenderShiftedBox {
|
||||
if (_alignment == value)
|
||||
return;
|
||||
_alignment = value;
|
||||
_applyUpdate();
|
||||
_markNeedResolution();
|
||||
}
|
||||
|
||||
/// The text direction with which to resolve [alignment].
|
||||
///
|
||||
/// This may be changed to null, but only after [alignment] has been changed
|
||||
/// to a value that does not depend on the direction.
|
||||
TextDirection get textDirection => _textDirection;
|
||||
TextDirection _textDirection;
|
||||
set textDirection(TextDirection value) {
|
||||
if (_textDirection == value)
|
||||
return;
|
||||
_textDirection = value;
|
||||
_applyUpdate();
|
||||
_markNeedResolution();
|
||||
}
|
||||
|
||||
/// Apply the current [alignment] to the [child].
|
||||
@ -285,10 +296,12 @@ abstract class RenderAligningShiftedBox extends RenderShiftedBox {
|
||||
/// This method must be called after the child has been laid out and
|
||||
/// this object's own size has been set.
|
||||
void alignChild() {
|
||||
_resolve();
|
||||
assert(child != null);
|
||||
assert(!child.debugNeedsLayout);
|
||||
assert(child.hasSize);
|
||||
assert(hasSize);
|
||||
assert(_resolvedAlignment != null);
|
||||
final BoxParentData childParentData = child.parentData;
|
||||
childParentData.offset = _resolvedAlignment.alongOffset(size - child.size);
|
||||
}
|
||||
|
@ -37,22 +37,26 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
|
||||
_padding = padding,
|
||||
_textDirection = textDirection {
|
||||
this.child = 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();
|
||||
}
|
||||
void _resolve() {
|
||||
if (_resolvedPadding != null)
|
||||
return;
|
||||
_resolvedPadding = padding.resolve(textDirection);
|
||||
assert(_resolvedPadding.isNonNegative);
|
||||
}
|
||||
|
||||
void _markNeedResolution() {
|
||||
_resolvedPadding = null;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
/// The amount to pad the child in each dimension.
|
||||
///
|
||||
/// If this is set to an [EdgeInsetsDirectional] object, then [textDirection]
|
||||
/// must not be null.
|
||||
EdgeInsetsGeometry get padding => _padding;
|
||||
EdgeInsetsGeometry _padding;
|
||||
set padding(EdgeInsetsGeometry value) {
|
||||
@ -61,17 +65,20 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
|
||||
if (_padding == value)
|
||||
return;
|
||||
_padding = value;
|
||||
_applyUpdate();
|
||||
_markNeedResolution();
|
||||
}
|
||||
|
||||
/// The text direction with which to resolve [padding].
|
||||
///
|
||||
/// This may be changed to null, but only after the [padding] has been changed
|
||||
/// to a value that does not depend on the direction.
|
||||
TextDirection get textDirection => _textDirection;
|
||||
TextDirection _textDirection;
|
||||
set textDirection(TextDirection value) {
|
||||
if (_textDirection == value)
|
||||
return;
|
||||
_textDirection = value;
|
||||
_applyUpdate();
|
||||
_markNeedResolution();
|
||||
}
|
||||
|
||||
/// The padding in the scroll direction on the side nearest the 0.0 scroll direction.
|
||||
@ -82,6 +89,7 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
|
||||
assert(constraints != null);
|
||||
assert(constraints.axisDirection != null);
|
||||
assert(constraints.growthDirection != null);
|
||||
assert(_resolvedPadding != null);
|
||||
switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
|
||||
case AxisDirection.up:
|
||||
return _resolvedPadding.bottom;
|
||||
@ -103,6 +111,7 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
|
||||
assert(constraints != null);
|
||||
assert(constraints.axisDirection != null);
|
||||
assert(constraints.growthDirection != null);
|
||||
assert(_resolvedPadding != null);
|
||||
switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
|
||||
case AxisDirection.up:
|
||||
return _resolvedPadding.top;
|
||||
@ -125,6 +134,7 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
|
||||
double get mainAxisPadding {
|
||||
assert(constraints != null);
|
||||
assert(constraints.axis != null);
|
||||
assert(_resolvedPadding != null);
|
||||
return _resolvedPadding.along(constraints.axis);
|
||||
}
|
||||
|
||||
@ -137,6 +147,7 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
|
||||
double get crossAxisPadding {
|
||||
assert(constraints != null);
|
||||
assert(constraints.axis != null);
|
||||
assert(_resolvedPadding != null);
|
||||
switch (constraints.axis) {
|
||||
case Axis.horizontal:
|
||||
return _resolvedPadding.vertical;
|
||||
@ -154,6 +165,8 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
_resolve();
|
||||
assert(_resolvedPadding != null);
|
||||
final double beforePadding = this.beforePadding;
|
||||
final double afterPadding = this.afterPadding;
|
||||
final double mainAxisPadding = this.mainAxisPadding;
|
||||
@ -248,6 +261,7 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
|
||||
assert(constraints != null);
|
||||
assert(constraints.axisDirection != null);
|
||||
assert(constraints.growthDirection != null);
|
||||
assert(_resolvedPadding != null);
|
||||
switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
|
||||
case AxisDirection.up:
|
||||
case AxisDirection.down:
|
||||
|
@ -309,7 +309,6 @@ class RenderStack extends RenderBox
|
||||
_fit = fit,
|
||||
_overflow = overflow {
|
||||
addAll(children);
|
||||
_applyUpdate();
|
||||
}
|
||||
|
||||
bool _hasVisualOverflow = false;
|
||||
@ -320,15 +319,17 @@ class RenderStack extends RenderBox
|
||||
child.parentData = new StackParentData();
|
||||
}
|
||||
|
||||
// The resolved absolute insets.
|
||||
FractionalOffset _resolvedAlignment;
|
||||
|
||||
void _applyUpdate() {
|
||||
final FractionalOffset resolvedAlignment = alignment.resolve(textDirection);
|
||||
if (_resolvedAlignment != resolvedAlignment) {
|
||||
_resolvedAlignment = resolvedAlignment;
|
||||
markNeedsLayout();
|
||||
}
|
||||
void _resolve() {
|
||||
if (_resolvedAlignment != null)
|
||||
return;
|
||||
_resolvedAlignment = alignment.resolve(textDirection);
|
||||
}
|
||||
|
||||
void _markNeedResolution() {
|
||||
_resolvedAlignment = null;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
/// How to align the non-positioned children in the stack.
|
||||
@ -337,24 +338,30 @@ class RenderStack extends RenderBox
|
||||
/// the points determined by [alignment] are co-located. For example, if the
|
||||
/// [alignment] is [FractionalOffset.topLeft], then the top left corner of
|
||||
/// each non-positioned child will be located at the same global coordinate.
|
||||
///
|
||||
/// If this is set to a [FractionalOffsetDirectional] object, then
|
||||
/// [textDirection] must not be null.
|
||||
FractionalOffsetGeometry get alignment => _alignment;
|
||||
FractionalOffsetGeometry _alignment;
|
||||
set alignment(FractionalOffsetGeometry value) {
|
||||
assert(value != null);
|
||||
if (_alignment != value) {
|
||||
_alignment = value;
|
||||
_applyUpdate();
|
||||
}
|
||||
if (_alignment == value)
|
||||
return;
|
||||
_alignment = value;
|
||||
_markNeedResolution();
|
||||
}
|
||||
|
||||
/// The text direction with which to resolve [alignment].
|
||||
///
|
||||
/// This may be changed to null, but only after the [alignment] has been changed
|
||||
/// to a value that does not depend on the direction.
|
||||
TextDirection get textDirection => _textDirection;
|
||||
TextDirection _textDirection;
|
||||
set textDirection(TextDirection value) {
|
||||
if (_textDirection != value) {
|
||||
_textDirection = value;
|
||||
_applyUpdate();
|
||||
}
|
||||
if (_textDirection == value)
|
||||
return;
|
||||
_textDirection = value;
|
||||
_markNeedResolution();
|
||||
}
|
||||
|
||||
/// How to size the non-positioned children in the stack.
|
||||
@ -426,6 +433,8 @@ class RenderStack extends RenderBox
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
_resolve();
|
||||
assert(_resolvedAlignment != null);
|
||||
_hasVisualOverflow = false;
|
||||
bool hasNonPositionedChildren = false;
|
||||
|
||||
|
@ -6,7 +6,7 @@ import 'dart:async';
|
||||
import 'dart:io' show File;
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui show Image;
|
||||
import 'dart:ui' show Size, Locale, hashValues;
|
||||
import 'dart:ui' show Size, Locale, TextDirection, hashValues;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
@ -36,8 +36,9 @@ class ImageConfiguration {
|
||||
this.bundle,
|
||||
this.devicePixelRatio,
|
||||
this.locale,
|
||||
this.textDirection,
|
||||
this.size,
|
||||
this.platform
|
||||
this.platform,
|
||||
});
|
||||
|
||||
/// Creates an object holding the configuration information for an [ImageProvider].
|
||||
@ -48,15 +49,17 @@ class ImageConfiguration {
|
||||
AssetBundle bundle,
|
||||
double devicePixelRatio,
|
||||
Locale locale,
|
||||
TextDirection textDirection,
|
||||
Size size,
|
||||
String platform
|
||||
String platform,
|
||||
}) {
|
||||
return new ImageConfiguration(
|
||||
bundle: bundle ?? this.bundle,
|
||||
devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio,
|
||||
locale: locale ?? this.locale,
|
||||
textDirection: textDirection ?? this.textDirection,
|
||||
size: size ?? this.size,
|
||||
platform: platform ?? this.platform
|
||||
platform: platform ?? this.platform,
|
||||
);
|
||||
}
|
||||
|
||||
@ -70,6 +73,9 @@ class ImageConfiguration {
|
||||
/// The language and region for which to select the image.
|
||||
final Locale locale;
|
||||
|
||||
/// The reading direction of the language for which to select the image.
|
||||
final TextDirection textDirection;
|
||||
|
||||
/// The size at which the image will be rendered.
|
||||
final Size size;
|
||||
|
||||
@ -92,6 +98,7 @@ class ImageConfiguration {
|
||||
return typedOther.bundle == bundle
|
||||
&& typedOther.devicePixelRatio == devicePixelRatio
|
||||
&& typedOther.locale == locale
|
||||
&& typedOther.textDirection == textDirection
|
||||
&& typedOther.size == size
|
||||
&& typedOther.platform == platform;
|
||||
}
|
||||
@ -122,6 +129,12 @@ class ImageConfiguration {
|
||||
result.write('locale: $locale');
|
||||
hasArguments = true;
|
||||
}
|
||||
if (textDirection != null) {
|
||||
if (hasArguments)
|
||||
result.write(', ');
|
||||
result.write('textDirection: $textDirection');
|
||||
hasArguments = true;
|
||||
}
|
||||
if (size != null) {
|
||||
if (hasArguments)
|
||||
result.write(', ');
|
||||
|
@ -224,8 +224,9 @@ class AssetImage extends AssetBundleImageProvider {
|
||||
final SplayTreeMap<double, String> mapping = new SplayTreeMap<double, String>();
|
||||
for (String candidate in candidates)
|
||||
mapping[_parseScale(candidate)] = candidate;
|
||||
// TODO(ianh): implement support for config.locale, config.size, config.platform
|
||||
// (then document this over in the Image.asset docs)
|
||||
// TODO(ianh): implement support for config.locale, config.textDirection,
|
||||
// config.size, config.platform (then document this over in the Image.asset
|
||||
// docs)
|
||||
return _findNearest(mapping, config.devicePixelRatio);
|
||||
}
|
||||
|
||||
|
@ -161,8 +161,8 @@ class AnimatedList extends StatefulWidget {
|
||||
/// AnimatedListState animatedList = AnimatedList.of(context);
|
||||
/// ```
|
||||
static AnimatedListState of(BuildContext context, { bool nullOk: false }) {
|
||||
assert(nullOk != null);
|
||||
assert(context != null);
|
||||
assert(nullOk != null);
|
||||
final AnimatedListState result = context.ancestorStateOfType(const TypeMatcher<AnimatedListState>());
|
||||
if (nullOk || result != null)
|
||||
return result;
|
||||
|
@ -3826,7 +3826,8 @@ class RichText extends LeafRenderObjectWidget {
|
||||
class RawImage extends LeafRenderObjectWidget {
|
||||
/// Creates a widget that displays an image.
|
||||
///
|
||||
/// The [scale] and [repeat] arguments must not be null.
|
||||
/// The [scale], [alignment], [repeat], and [matchTextDirection] arguments must
|
||||
/// not be null.
|
||||
const RawImage({
|
||||
Key key,
|
||||
this.image,
|
||||
@ -3836,11 +3837,14 @@ class RawImage extends LeafRenderObjectWidget {
|
||||
this.color,
|
||||
this.colorBlendMode,
|
||||
this.fit,
|
||||
this.alignment,
|
||||
this.alignment: FractionalOffset.center,
|
||||
this.repeat: ImageRepeat.noRepeat,
|
||||
this.centerSlice
|
||||
this.centerSlice,
|
||||
this.matchTextDirection: false,
|
||||
}) : assert(scale != null),
|
||||
assert(alignment != null),
|
||||
assert(repeat != null),
|
||||
assert(matchTextDirection != null),
|
||||
super(key: key);
|
||||
|
||||
/// The image to display.
|
||||
@ -3884,10 +3888,23 @@ class RawImage extends LeafRenderObjectWidget {
|
||||
|
||||
/// How to align the image within its bounds.
|
||||
///
|
||||
/// An alignment of (0.0, 0.0) aligns the image to the top-left corner of its
|
||||
/// layout bounds. An alignment of (1.0, 0.5) aligns the image to the middle
|
||||
/// of the right edge of its layout bounds.
|
||||
final FractionalOffset alignment;
|
||||
/// The alignment aligns the given position in the image to the given position
|
||||
/// in the layout bounds. For example, a [FractionalOffset] alignment of (0.0,
|
||||
/// 0.0) aligns the image to the top-left corner of its layout bounds, while a
|
||||
/// [FractionalOffset] alignment of (1.0, 1.0) aligns the bottom right of the
|
||||
/// image with the bottom right corner of its layout bounds. Similarly, an
|
||||
/// alignment of (0.5, 1.0) aligns the bottom middle of the image with the
|
||||
/// middle of the bottom edge of its layout bounds.
|
||||
///
|
||||
/// To display a subpart of an image, consider using a [CustomPainter] and
|
||||
/// [Canvas.drawImageRect].
|
||||
///
|
||||
/// If the [alignment] is [TextDirection]-dependent (i.e. if it is a
|
||||
/// [FractionalOffsetDirectional]), then an ambient [Directionality] widget
|
||||
/// must be in scope.
|
||||
///
|
||||
/// Defaults to [FractionalOffset.center].
|
||||
final FractionalOffsetGeometry alignment;
|
||||
|
||||
/// How to paint any portions of the layout bounds not covered by the image.
|
||||
final ImageRepeat repeat;
|
||||
@ -3901,8 +3918,26 @@ class RawImage extends LeafRenderObjectWidget {
|
||||
/// the center slice will be stretched only vertically.
|
||||
final Rect centerSlice;
|
||||
|
||||
/// Whether to paint the image in the direction of the [TextDirection].
|
||||
///
|
||||
/// If this is true, then in [TextDirection.ltr] contexts, the image will be
|
||||
/// drawn with its origin in the top left (the "normal" painting direction for
|
||||
/// images); and in [TextDirection.rtl] contexts, the image will be drawn with
|
||||
/// a scaling factor of -1 in the horizontal direction so that the origin is
|
||||
/// in the top right.
|
||||
///
|
||||
/// This is occasionally used with images in right-to-left environments, for
|
||||
/// images that were designed for left-to-right locales. Be careful, when
|
||||
/// using this, to not flip images with integral shadows, text, or other
|
||||
/// effects that will look incorrect when flipped.
|
||||
///
|
||||
/// If this is true, there must be an ambient [Directionality] widget in
|
||||
/// scope.
|
||||
final bool matchTextDirection;
|
||||
|
||||
@override
|
||||
RenderImage createRenderObject(BuildContext context) {
|
||||
assert((!matchTextDirection && alignment is FractionalOffset) || debugCheckHasDirectionality(context));
|
||||
return new RenderImage(
|
||||
image: image,
|
||||
width: width,
|
||||
@ -3913,7 +3948,9 @@ class RawImage extends LeafRenderObjectWidget {
|
||||
fit: fit,
|
||||
alignment: alignment,
|
||||
repeat: repeat,
|
||||
centerSlice: centerSlice
|
||||
centerSlice: centerSlice,
|
||||
matchTextDirection: matchTextDirection,
|
||||
textDirection: matchTextDirection || alignment is! FractionalOffset ? Directionality.of(context) : null,
|
||||
);
|
||||
}
|
||||
|
||||
@ -3929,7 +3966,9 @@ class RawImage extends LeafRenderObjectWidget {
|
||||
..alignment = alignment
|
||||
..fit = fit
|
||||
..repeat = repeat
|
||||
..centerSlice = centerSlice;
|
||||
..centerSlice = centerSlice
|
||||
..matchTextDirection = matchTextDirection
|
||||
..textDirection = matchTextDirection || alignment is! FractionalOffset ? Directionality.of(context) : null;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -3942,9 +3981,10 @@ class RawImage extends LeafRenderObjectWidget {
|
||||
description.add(new DiagnosticsProperty<Color>('color', color, defaultValue: null));
|
||||
description.add(new EnumProperty<BlendMode>('colorBlendMode', colorBlendMode, defaultValue: null));
|
||||
description.add(new EnumProperty<BoxFit>('fit', fit, defaultValue: null));
|
||||
description.add(new DiagnosticsProperty<FractionalOffset>('alignment', alignment, defaultValue: null));
|
||||
description.add(new DiagnosticsProperty<FractionalOffsetGeometry>('alignment', alignment, defaultValue: null));
|
||||
description.add(new EnumProperty<ImageRepeat>('repeat', repeat, defaultValue: ImageRepeat.noRepeat));
|
||||
description.add(new DiagnosticsProperty<Rect>('centerSlice', centerSlice, defaultValue: null));
|
||||
description.add(new FlagProperty('matchTextDirection', value: matchTextDirection, ifTrue: 'match text direction'));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,7 +71,7 @@ class DecoratedBox extends SingleChildRenderObjectWidget {
|
||||
return new RenderDecoratedBox(
|
||||
decoration: decoration,
|
||||
position: position,
|
||||
configuration: createLocalImageConfiguration(context)
|
||||
configuration: createLocalImageConfiguration(context),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -60,7 +60,8 @@ class FadeInImage extends StatefulWidget {
|
||||
/// then cross-fades to display the [image].
|
||||
///
|
||||
/// The [placeholder], [image], [fadeOutDuration], [fadeOutCurve],
|
||||
/// [fadeInDuration], [fadeInCurve] and [repeat] arguments must not be null.
|
||||
/// [fadeInDuration], [fadeInCurve], [alignment], [repeat], and
|
||||
/// [matchTextDirection] arguments must not be null.
|
||||
const FadeInImage({
|
||||
Key key,
|
||||
@required this.placeholder,
|
||||
@ -72,15 +73,18 @@ class FadeInImage extends StatefulWidget {
|
||||
this.width,
|
||||
this.height,
|
||||
this.fit,
|
||||
this.alignment,
|
||||
this.alignment: FractionalOffset.center,
|
||||
this.repeat: ImageRepeat.noRepeat,
|
||||
this.matchTextDirection: false,
|
||||
}) : assert(placeholder != null),
|
||||
assert(image != null),
|
||||
assert(fadeOutDuration != null),
|
||||
assert(fadeOutCurve != null),
|
||||
assert(fadeInDuration != null),
|
||||
assert(fadeInCurve != null),
|
||||
assert(alignment != null),
|
||||
assert(repeat != null),
|
||||
assert(matchTextDirection != null),
|
||||
super(key: key);
|
||||
|
||||
/// Creates a widget that uses a placeholder image stored in memory while
|
||||
@ -94,8 +98,9 @@ class FadeInImage extends StatefulWidget {
|
||||
/// [ImageProvider]s (see also [ImageInfo.scale]).
|
||||
///
|
||||
/// The [placeholder], [image], [placeholderScale], [imageScale],
|
||||
/// [fadeOutDuration], [fadeOutCurve], [fadeInDuration], [fadeInCurve] and
|
||||
/// [repeat] arguments must not be null.
|
||||
/// [fadeOutDuration], [fadeOutCurve], [fadeInDuration], [fadeInCurve],
|
||||
/// [alignment], [repeat], and [matchTextDirection] arguments must not be
|
||||
/// null.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
@ -116,8 +121,9 @@ class FadeInImage extends StatefulWidget {
|
||||
this.width,
|
||||
this.height,
|
||||
this.fit,
|
||||
this.alignment,
|
||||
this.alignment: FractionalOffset.center,
|
||||
this.repeat: ImageRepeat.noRepeat,
|
||||
this.matchTextDirection: false,
|
||||
}) : assert(placeholder != null),
|
||||
assert(image != null),
|
||||
assert(placeholderScale != null),
|
||||
@ -126,7 +132,9 @@ class FadeInImage extends StatefulWidget {
|
||||
assert(fadeOutCurve != null),
|
||||
assert(fadeInDuration != null),
|
||||
assert(fadeInCurve != null),
|
||||
assert(alignment != null),
|
||||
assert(repeat != null),
|
||||
assert(matchTextDirection != null),
|
||||
placeholder = new MemoryImage(placeholder, scale: placeholderScale),
|
||||
image = new NetworkImage(image, scale: imageScale),
|
||||
super(key: key);
|
||||
@ -146,8 +154,8 @@ class FadeInImage extends StatefulWidget {
|
||||
/// exact asset specified will be used.
|
||||
///
|
||||
/// The [placeholder], [image], [imageScale], [fadeOutDuration],
|
||||
/// [fadeOutCurve], [fadeInDuration], [fadeInCurve] and [repeat] arguments
|
||||
/// must not be null.
|
||||
/// [fadeOutCurve], [fadeInDuration], [fadeInCurve], [alignment], [repeat],
|
||||
/// and [matchTextDirection] arguments must not be null.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
@ -169,8 +177,9 @@ class FadeInImage extends StatefulWidget {
|
||||
this.width,
|
||||
this.height,
|
||||
this.fit,
|
||||
this.alignment,
|
||||
this.alignment: FractionalOffset.center,
|
||||
this.repeat: ImageRepeat.noRepeat,
|
||||
this.matchTextDirection: false,
|
||||
}) : assert(placeholder != null),
|
||||
assert(image != null),
|
||||
placeholder = placeholderScale != null
|
||||
@ -181,7 +190,9 @@ class FadeInImage extends StatefulWidget {
|
||||
assert(fadeOutCurve != null),
|
||||
assert(fadeInDuration != null),
|
||||
assert(fadeInCurve != null),
|
||||
assert(alignment != null),
|
||||
assert(repeat != null),
|
||||
assert(matchTextDirection != null),
|
||||
image = new NetworkImage(image, scale: imageScale),
|
||||
super(key: key);
|
||||
|
||||
@ -227,14 +238,41 @@ class FadeInImage extends StatefulWidget {
|
||||
|
||||
/// How to align the image within its bounds.
|
||||
///
|
||||
/// An alignment of (0.0, 0.0) aligns the image to the top-left corner of its
|
||||
/// layout bounds. An alignment of (1.0, 0.5) aligns the image to the middle
|
||||
/// of the right edge of its layout bounds.
|
||||
final FractionalOffset alignment;
|
||||
/// The alignment aligns the given position in the image to the given position
|
||||
/// in the layout bounds. For example, a [FractionalOffset] alignment of (0.0,
|
||||
/// 0.0) aligns the image to the top-left corner of its layout bounds, while a
|
||||
/// [FractionalOffset] alignment of (1.0, 1.0) aligns the bottom right of the
|
||||
/// image with the bottom right corner of its layout bounds. Similarly, an
|
||||
/// alignment of (0.5, 1.0) aligns the bottom middle of the image with the
|
||||
/// middle of the bottom edge of its layout bounds.
|
||||
///
|
||||
/// If the [alignment] is [TextDirection]-dependent (i.e. if it is a
|
||||
/// [FractionalOffsetDirectional]), then an ambient [Directionality] widget
|
||||
/// must be in scope.
|
||||
///
|
||||
/// Defaults to [FractionalOffset.center].
|
||||
final FractionalOffsetGeometry alignment;
|
||||
|
||||
/// How to paint any portions of the layout bounds not covered by the image.
|
||||
final ImageRepeat repeat;
|
||||
|
||||
/// Whether to paint the image in the direction of the [TextDirection].
|
||||
///
|
||||
/// If this is true, then in [TextDirection.ltr] contexts, the image will be
|
||||
/// drawn with its origin in the top left (the "normal" painting direction for
|
||||
/// images); and in [TextDirection.rtl] contexts, the image will be drawn with
|
||||
/// a scaling factor of -1 in the horizontal direction so that the origin is
|
||||
/// in the top right.
|
||||
///
|
||||
/// This is occasionally used with images in right-to-left environments, for
|
||||
/// images that were designed for left-to-right locales. Be careful, when
|
||||
/// using this, to not flip images with integral shadows, text, or other
|
||||
/// effects that will look incorrect when flipped.
|
||||
///
|
||||
/// If this is true, there must be an ambient [Directionality] widget in
|
||||
/// scope.
|
||||
final bool matchTextDirection;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => new _FadeInImageState();
|
||||
}
|
||||
@ -282,8 +320,8 @@ class _ImageProviderResolver {
|
||||
void resolve(ImageProvider provider) {
|
||||
final ImageStream oldImageStream = _imageStream;
|
||||
_imageStream = provider.resolve(createLocalImageConfiguration(
|
||||
state.context,
|
||||
size: widget.width != null && widget.height != null ? new Size(widget.width, widget.height) : null
|
||||
state.context,
|
||||
size: widget.width != null && widget.height != null ? new Size(widget.width, widget.height) : null
|
||||
));
|
||||
assert(_imageStream != null);
|
||||
|
||||
@ -456,6 +494,7 @@ class _FadeInImageState extends State<FadeInImage> with TickerProviderStateMixin
|
||||
fit: widget.fit,
|
||||
alignment: widget.alignment,
|
||||
repeat: widget.repeat,
|
||||
matchTextDirection: widget.matchTextDirection,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ import 'package:flutter/services.dart';
|
||||
|
||||
import 'basic.dart';
|
||||
import 'framework.dart';
|
||||
import 'localizations.dart';
|
||||
import 'media_query.dart';
|
||||
|
||||
export 'package:flutter/services.dart' show
|
||||
@ -39,7 +40,8 @@ ImageConfiguration createLocalImageConfiguration(BuildContext context, { Size si
|
||||
return new ImageConfiguration(
|
||||
bundle: DefaultAssetBundle.of(context),
|
||||
devicePixelRatio: MediaQuery.of(context, nullOk: true)?.devicePixelRatio ?? 1.0,
|
||||
// TODO(ianh): provide the locale
|
||||
locale: Localizations.localeOf(context, nullOk: true),
|
||||
textDirection: Directionality.of(context),
|
||||
size: size,
|
||||
platform: defaultTargetPlatform,
|
||||
);
|
||||
@ -101,7 +103,8 @@ class Image extends StatefulWidget {
|
||||
/// To show an image from the network or from an asset bundle, consider using
|
||||
/// [new Image.network] and [new Image.asset] respectively.
|
||||
///
|
||||
/// The [image] and [repeat] arguments must not be null.
|
||||
/// The [image], [alignment], [repeat], and [matchTextDirection] arguments
|
||||
/// must not be null.
|
||||
const Image({
|
||||
Key key,
|
||||
@required this.image,
|
||||
@ -110,12 +113,16 @@ class Image extends StatefulWidget {
|
||||
this.color,
|
||||
this.colorBlendMode,
|
||||
this.fit,
|
||||
this.alignment,
|
||||
this.alignment: FractionalOffset.center,
|
||||
this.repeat: ImageRepeat.noRepeat,
|
||||
this.centerSlice,
|
||||
this.matchTextDirection: false,
|
||||
this.gaplessPlayback: false,
|
||||
this.package,
|
||||
}) : assert(image != null),
|
||||
assert(alignment != null),
|
||||
assert(repeat != null),
|
||||
assert(matchTextDirection != null),
|
||||
super(key: key);
|
||||
|
||||
/// Creates a widget that displays an [ImageStream] obtained from the network.
|
||||
@ -129,12 +136,16 @@ class Image extends StatefulWidget {
|
||||
this.color,
|
||||
this.colorBlendMode,
|
||||
this.fit,
|
||||
this.alignment,
|
||||
this.alignment: FractionalOffset.center,
|
||||
this.repeat: ImageRepeat.noRepeat,
|
||||
this.centerSlice,
|
||||
this.matchTextDirection: false,
|
||||
this.gaplessPlayback: false,
|
||||
this.package,
|
||||
}) : image = new NetworkImage(src, scale: scale),
|
||||
assert(alignment != null),
|
||||
assert(repeat != null),
|
||||
assert(matchTextDirection != null),
|
||||
super(key: key);
|
||||
|
||||
/// Creates a widget that displays an [ImageStream] obtained from a [File].
|
||||
@ -151,12 +162,16 @@ class Image extends StatefulWidget {
|
||||
this.color,
|
||||
this.colorBlendMode,
|
||||
this.fit,
|
||||
this.alignment,
|
||||
this.alignment: FractionalOffset.center,
|
||||
this.repeat: ImageRepeat.noRepeat,
|
||||
this.centerSlice,
|
||||
this.matchTextDirection: false,
|
||||
this.gaplessPlayback: false,
|
||||
this.package,
|
||||
}) : image = new FileImage(file, scale: scale),
|
||||
assert(alignment != null),
|
||||
assert(repeat != null),
|
||||
assert(matchTextDirection != null),
|
||||
super(key: key);
|
||||
|
||||
/// Creates a widget that displays an [ImageStream] obtained from an asset
|
||||
@ -181,8 +196,9 @@ class Image extends StatefulWidget {
|
||||
// /// size-aware asset resolution will be attempted also, with the given
|
||||
// /// dimensions interpreted as logical pixels.
|
||||
// ///
|
||||
// /// * If the images have platform or locale variants, the current platform
|
||||
// /// and locale is taken into account during asset resolution as well.
|
||||
// /// * If the images have platform, locale, or directionality variants, the
|
||||
// /// current platform, locale, and directionality are taken into account
|
||||
// /// during asset resolution as well.
|
||||
///
|
||||
/// The [name] and [repeat] arguments must not be null.
|
||||
///
|
||||
@ -277,15 +293,19 @@ class Image extends StatefulWidget {
|
||||
this.color,
|
||||
this.colorBlendMode,
|
||||
this.fit,
|
||||
this.alignment,
|
||||
this.alignment: FractionalOffset.center,
|
||||
this.repeat: ImageRepeat.noRepeat,
|
||||
this.centerSlice,
|
||||
this.matchTextDirection: false,
|
||||
this.gaplessPlayback: false,
|
||||
this.package,
|
||||
}) : image = scale != null
|
||||
? new ExactAssetImage(name, bundle: bundle, scale: scale, package: package)
|
||||
: new AssetImage(name, bundle: bundle, package: package),
|
||||
super(key: key);
|
||||
? new ExactAssetImage(name, bundle: bundle, scale: scale, package: package)
|
||||
: new AssetImage(name, bundle: bundle, package: package),
|
||||
assert(alignment != null),
|
||||
assert(repeat != null),
|
||||
assert(matchTextDirection != null),
|
||||
super(key: key);
|
||||
|
||||
/// Creates a widget that displays an [ImageStream] obtained from a [Uint8List].
|
||||
///
|
||||
@ -298,12 +318,16 @@ class Image extends StatefulWidget {
|
||||
this.color,
|
||||
this.colorBlendMode,
|
||||
this.fit,
|
||||
this.alignment,
|
||||
this.alignment: FractionalOffset.center,
|
||||
this.repeat: ImageRepeat.noRepeat,
|
||||
this.centerSlice,
|
||||
this.matchTextDirection: false,
|
||||
this.gaplessPlayback: false,
|
||||
this.package,
|
||||
}) : image = new MemoryImage(bytes, scale: scale),
|
||||
assert(alignment != null),
|
||||
assert(repeat != null),
|
||||
assert(matchTextDirection != null),
|
||||
super(key: key);
|
||||
|
||||
/// The image to display.
|
||||
@ -342,10 +366,23 @@ class Image extends StatefulWidget {
|
||||
|
||||
/// How to align the image within its bounds.
|
||||
///
|
||||
/// An alignment of (0.0, 0.0) aligns the image to the top-left corner of its
|
||||
/// layout bounds. An alignment of (1.0, 0.5) aligns the image to the middle
|
||||
/// of the right edge of its layout bounds.
|
||||
final FractionalOffset alignment;
|
||||
/// The alignment aligns the given position in the image to the given position
|
||||
/// in the layout bounds. For example, a [FractionalOffset] alignment of (0.0,
|
||||
/// 0.0) aligns the image to the top-left corner of its layout bounds, while a
|
||||
/// [FractionalOffset] alignment of (1.0, 1.0) aligns the bottom right of the
|
||||
/// image with the bottom right corner of its layout bounds. Similarly, an
|
||||
/// alignment of (0.5, 1.0) aligns the bottom middle of the image with the
|
||||
/// middle of the bottom edge of its layout bounds.
|
||||
///
|
||||
/// To display a subpart of an image, consider using a [CustomPainter] and
|
||||
/// [Canvas.drawImageRect].
|
||||
///
|
||||
/// If the [alignment] is [TextDirection]-dependent (i.e. if it is a
|
||||
/// [FractionalOffsetDirectional]), then an ambient [Directionality] widget
|
||||
/// must be in scope.
|
||||
///
|
||||
/// Defaults to [FractionalOffset.center].
|
||||
final FractionalOffsetGeometry alignment;
|
||||
|
||||
/// How to paint any portions of the layout bounds not covered by the image.
|
||||
final ImageRepeat repeat;
|
||||
@ -359,6 +396,23 @@ class Image extends StatefulWidget {
|
||||
/// the center slice will be stretched only vertically.
|
||||
final Rect centerSlice;
|
||||
|
||||
/// Whether to paint the image in the direction of the [TextDirection].
|
||||
///
|
||||
/// If this is true, then in [TextDirection.ltr] contexts, the image will be
|
||||
/// drawn with its origin in the top left (the "normal" painting direction for
|
||||
/// images); and in [TextDirection.rtl] contexts, the image will be drawn with
|
||||
/// a scaling factor of -1 in the horizontal direction so that the origin is
|
||||
/// in the top right.
|
||||
///
|
||||
/// This is occasionally used with images in right-to-left environments, for
|
||||
/// images that were designed for left-to-right locales. Be careful, when
|
||||
/// using this, to not flip images with integral shadows, text, or other
|
||||
/// effects that will look incorrect when flipped.
|
||||
///
|
||||
/// If this is true, there must be an ambient [Directionality] widget in
|
||||
/// scope.
|
||||
final bool matchTextDirection;
|
||||
|
||||
/// Whether to continue showing the old image (true), or briefly show nothing
|
||||
/// (false), when the image provider changes.
|
||||
final bool gaplessPlayback;
|
||||
@ -379,9 +433,10 @@ class Image extends StatefulWidget {
|
||||
description.add(new DiagnosticsProperty<Color>('color', color, defaultValue: null));
|
||||
description.add(new EnumProperty<BlendMode>('colorBlendMode', colorBlendMode, defaultValue: null));
|
||||
description.add(new EnumProperty<BoxFit>('fit', fit, defaultValue: null));
|
||||
description.add(new DiagnosticsProperty<FractionalOffset>('alignment', alignment, defaultValue: null));
|
||||
description.add(new DiagnosticsProperty<FractionalOffsetGeometry>('alignment', alignment, defaultValue: null));
|
||||
description.add(new EnumProperty<ImageRepeat>('repeat', repeat, defaultValue: ImageRepeat.noRepeat));
|
||||
description.add(new DiagnosticsProperty<Rect>('centerSlice', centerSlice, defaultValue: null));
|
||||
description.add(new FlagProperty('matchTextDirection', value: matchTextDirection, ifTrue: 'match text direction'));
|
||||
}
|
||||
}
|
||||
|
||||
@ -448,7 +503,8 @@ class _ImageState extends State<Image> {
|
||||
fit: widget.fit,
|
||||
alignment: widget.alignment,
|
||||
repeat: widget.repeat,
|
||||
centerSlice: widget.centerSlice
|
||||
centerSlice: widget.centerSlice,
|
||||
matchTextDirection: widget.matchTextDirection,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -385,9 +385,16 @@ class Localizations extends StatefulWidget {
|
||||
|
||||
/// The locale of the Localizations widget for the widget tree that
|
||||
/// corresponds to [BuildContext] `context`.
|
||||
static Locale localeOf(BuildContext context) {
|
||||
///
|
||||
/// If no [Localizations] widget is in scope then the [Localizations.localeOf]
|
||||
/// method will throw an exception, unless the `nullOk` argument is set to
|
||||
/// true, in which case it returns null.
|
||||
static Locale localeOf(BuildContext context, { bool nullOk: false }) {
|
||||
assert(context != null);
|
||||
assert(nullOk != null);
|
||||
final _LocalizationsScope scope = context.inheritFromWidgetOfExactType(_LocalizationsScope);
|
||||
if (nullOk && scope == null)
|
||||
return null;
|
||||
assert(scope != null, 'a Localizations ancestor was not found');
|
||||
return scope.localizationsState.locale;
|
||||
}
|
||||
|
@ -173,6 +173,8 @@ class MediaQuery extends InheritedWidget {
|
||||
/// If you use this from a widget (e.g. in its build function), consider
|
||||
/// calling [debugCheckHasMediaQuery].
|
||||
static MediaQueryData of(BuildContext context, { bool nullOk: false }) {
|
||||
assert(context != null);
|
||||
assert(nullOk != null);
|
||||
final MediaQuery query = context.inheritFromWidgetOfExactType(MediaQuery);
|
||||
if (query != null)
|
||||
return query.data;
|
||||
|
@ -74,6 +74,7 @@ void main() {
|
||||
' constraints: BoxConstraints(25.0<=w<=100.0, 25.0<=h<=100.0)\n'
|
||||
' size: Size(25.0, 25.0)\n'
|
||||
' image: [10×10]\n'
|
||||
' alignment: FractionalOffset.center\n'
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui' as ui show Paragraph;
|
||||
import 'dart:ui' as ui show Paragraph, Image;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
@ -278,6 +278,24 @@ abstract class PaintPattern {
|
||||
/// If no call to [Canvas.drawParagraph] was made, then this results in failure.
|
||||
void paragraph({ ui.Paragraph paragraph, Offset offset });
|
||||
|
||||
/// Indicates that an image is expected next.
|
||||
///
|
||||
/// The next call to [Canvas.drawImageRect] is examined, and its arguments
|
||||
/// compared to those passed to _this_ method.
|
||||
///
|
||||
/// If no call to [Canvas.drawImageRect] was made, then this results in
|
||||
/// failure.
|
||||
///
|
||||
/// Any calls made between the last matched call (if any) and the
|
||||
/// [Canvas.drawImageRect] call are ignored.
|
||||
///
|
||||
/// The [Paint]-related arguments (`color`, `strokeWidth`, `hasMaskFilter`,
|
||||
/// `style`) are compared against the state of the [Paint] object after the
|
||||
/// painting has completed, not at the time of the call. If the same [Paint]
|
||||
/// object is reused multiple times, then this may not match the actual
|
||||
/// arguments as they were seen by the method.
|
||||
void drawImageRect({ ui.Image image, Rect source, Rect destination, Color color, double strokeWidth, bool hasMaskFilter, PaintingStyle style });
|
||||
|
||||
/// Provides a custom matcher.
|
||||
///
|
||||
/// Each method call after the last matched call (if any) will be passed to
|
||||
@ -472,6 +490,11 @@ class _TestRecordingCanvasPatternMatcher extends _TestRecordingCanvasMatcher imp
|
||||
_predicates.add(new _FunctionPaintPredicate(#drawParagraph, <dynamic>[paragraph, offset]));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawImageRect({ ui.Image image, Rect source, Rect destination, Color color, double strokeWidth, bool hasMaskFilter, PaintingStyle style }) {
|
||||
_predicates.add(new _DrawImageRectPaintPredicate(image: image, source: source, destination: destination, color: color, strokeWidth: strokeWidth, hasMaskFilter: hasMaskFilter, style: style));
|
||||
}
|
||||
|
||||
@override
|
||||
void something(PaintPatternPredicate predicate) {
|
||||
_predicates.add(new _SomethingPaintPredicate(predicate));
|
||||
@ -800,6 +823,41 @@ class _ArcPaintPredicate extends _DrawCommandPaintPredicate {
|
||||
);
|
||||
}
|
||||
|
||||
class _DrawImageRectPaintPredicate extends _DrawCommandPaintPredicate {
|
||||
_DrawImageRectPaintPredicate({ this.image, this.source, this.destination, Color color, double strokeWidth, bool hasMaskFilter, PaintingStyle style }) : super(
|
||||
#drawImageRect, 'an image', 4, 3, color: color, strokeWidth: strokeWidth, hasMaskFilter: hasMaskFilter, style: style
|
||||
);
|
||||
|
||||
final ui.Image image;
|
||||
final Rect source;
|
||||
final Rect destination;
|
||||
|
||||
@override
|
||||
void verifyArguments(List<dynamic> arguments) {
|
||||
super.verifyArguments(arguments);
|
||||
final ui.Image imageArgument = arguments[0];
|
||||
if (image != null && imageArgument != image)
|
||||
throw 'It called $methodName with an image, $imageArgument, which was not exactly the expected image ($image).';
|
||||
final Rect sourceArgument = arguments[1];
|
||||
if (source != null && sourceArgument != source)
|
||||
throw 'It called $methodName with a source rectangle, $sourceArgument, which was not exactly the expected rectangle ($source).';
|
||||
final Rect destinationArgument = arguments[2];
|
||||
if (destination != null && destinationArgument != destination)
|
||||
throw 'It called $methodName with a destination rectangle, $destinationArgument, which was not exactly the expected rectangle ($destination).';
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillDescription(List<String> description) {
|
||||
super.debugFillDescription(description);
|
||||
if (image != null)
|
||||
description.add('image $image');
|
||||
if (source != null)
|
||||
description.add('source $source');
|
||||
if (destination != null)
|
||||
description.add('destination $destination');
|
||||
}
|
||||
}
|
||||
|
||||
class _SomethingPaintPredicate extends _PaintPredicate {
|
||||
_SomethingPaintPredicate(this.predicate);
|
||||
|
||||
|
@ -21,6 +21,26 @@ void main() {
|
||||
alignment: const FractionalOffset(0.5, 0.5),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
const Align(
|
||||
key: const GlobalObjectKey<Null>(null),
|
||||
alignment: FractionalOffset.topLeft,
|
||||
),
|
||||
);
|
||||
await tester.pumpWidget(const Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: const Align(
|
||||
key: const GlobalObjectKey<Null>(null),
|
||||
alignment: FractionalOffsetDirectional.topStart,
|
||||
),
|
||||
));
|
||||
await tester.pumpWidget(
|
||||
const Align(
|
||||
key: const GlobalObjectKey<Null>(null),
|
||||
alignment: FractionalOffset.topLeft,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Align control test (LTR)', (WidgetTester tester) async {
|
||||
|
583
packages/flutter/test/widgets/image_rtl_test.dart
Normal file
583
packages/flutter/test/widgets/image_rtl_test.dart
Normal file
@ -0,0 +1,583 @@
|
||||
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:ui' as ui show Image;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../rendering/mock_canvas.dart';
|
||||
|
||||
class TestImageProvider extends ImageProvider<TestImageProvider> {
|
||||
@override
|
||||
Future<TestImageProvider> obtainKey(ImageConfiguration configuration) {
|
||||
return new SynchronousFuture<TestImageProvider>(this);
|
||||
}
|
||||
|
||||
@override
|
||||
ImageStreamCompleter load(TestImageProvider key) {
|
||||
return new OneFrameImageStreamCompleter(
|
||||
new SynchronousFuture<ImageInfo>(new ImageInfo(image: new TestImage()))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TestImage extends ui.Image {
|
||||
@override
|
||||
int get width => 16;
|
||||
|
||||
@override
|
||||
int get height => 9;
|
||||
|
||||
// @override
|
||||
// void dispose() { }
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('DecorationImage RTL with alignment topEnd and match', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: new Center(
|
||||
child: new Container(
|
||||
width: 100.0,
|
||||
height: 50.0,
|
||||
decoration: new BoxDecoration(
|
||||
image: new DecorationImage(
|
||||
image: new TestImageProvider(),
|
||||
alignment: FractionalOffsetDirectional.topEnd,
|
||||
repeat: ImageRepeat.repeatX,
|
||||
matchTextDirection: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Duration.ZERO,
|
||||
EnginePhase.layout, // so that we don't try to paint the fake images
|
||||
);
|
||||
expect(find.byType(Container), paints
|
||||
..clipRect(rect: new Rect.fromLTRB(0.0, 0.0, 100.0, 50.0))
|
||||
..translate(x: 50.0, y: 0.0)
|
||||
..scale(x: -1.0, y: 1.0)
|
||||
..translate(x: -50.0, y: 0.0)
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(-12.0, 0.0, 4.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(4.0, 0.0, 20.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(20.0, 0.0, 36.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(36.0, 0.0, 52.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(52.0, 0.0, 68.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(68.0, 0.0, 84.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(84.0, 0.0, 100.0, 9.0))
|
||||
..restore()
|
||||
);
|
||||
expect(find.byType(Container), isNot(paints..scale()..scale()));
|
||||
});
|
||||
|
||||
testWidgets('DecorationImage LTR with alignment topEnd (and pointless match)', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Center(
|
||||
child: new Container(
|
||||
width: 100.0,
|
||||
height: 50.0,
|
||||
decoration: new BoxDecoration(
|
||||
image: new DecorationImage(
|
||||
image: new TestImageProvider(),
|
||||
alignment: FractionalOffsetDirectional.topEnd,
|
||||
repeat: ImageRepeat.repeatX,
|
||||
matchTextDirection: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Duration.ZERO,
|
||||
EnginePhase.layout, // so that we don't try to paint the fake images
|
||||
);
|
||||
expect(find.byType(Container), paints
|
||||
..clipRect(rect: new Rect.fromLTRB(0.0, 0.0, 100.0, 50.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(-12.0, 0.0, 4.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(4.0, 0.0, 20.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(20.0, 0.0, 36.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(36.0, 0.0, 52.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(52.0, 0.0, 68.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(68.0, 0.0, 84.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(84.0, 0.0, 100.0, 9.0))
|
||||
..restore()
|
||||
);
|
||||
expect(find.byType(Container), isNot(paints..scale()));
|
||||
});
|
||||
|
||||
testWidgets('DecorationImage RTL with alignment topEnd', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: new Center(
|
||||
child: new Container(
|
||||
width: 100.0,
|
||||
height: 50.0,
|
||||
decoration: new BoxDecoration(
|
||||
image: new DecorationImage(
|
||||
image: new TestImageProvider(),
|
||||
alignment: FractionalOffsetDirectional.topEnd,
|
||||
repeat: ImageRepeat.repeatX,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Duration.ZERO,
|
||||
EnginePhase.layout, // so that we don't try to paint the fake images
|
||||
);
|
||||
expect(find.byType(Container), paints
|
||||
..clipRect(rect: new Rect.fromLTRB(0.0, 0.0, 100.0, 50.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(16.0, 0.0, 32.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(32.0, 0.0, 48.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(48.0, 0.0, 64.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(64.0, 0.0, 80.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(80.0, 0.0, 96.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(96.0, 0.0, 112.0, 9.0))
|
||||
..restore()
|
||||
);
|
||||
expect(find.byType(Container), isNot(paints..scale()));
|
||||
});
|
||||
|
||||
testWidgets('DecorationImage LTR with alignment topEnd', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Center(
|
||||
child: new Container(
|
||||
width: 100.0,
|
||||
height: 50.0,
|
||||
decoration: new BoxDecoration(
|
||||
image: new DecorationImage(
|
||||
image: new TestImageProvider(),
|
||||
alignment: FractionalOffsetDirectional.topEnd,
|
||||
repeat: ImageRepeat.repeatX,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Duration.ZERO,
|
||||
EnginePhase.layout, // so that we don't try to paint the fake images
|
||||
);
|
||||
expect(find.byType(Container), paints
|
||||
..clipRect(rect: new Rect.fromLTRB(0.0, 0.0, 100.0, 50.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(-12.0, 0.0, 4.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(4.0, 0.0, 20.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(20.0, 0.0, 36.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(36.0, 0.0, 52.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(52.0, 0.0, 68.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(68.0, 0.0, 84.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(84.0, 0.0, 100.0, 9.0))
|
||||
..restore()
|
||||
);
|
||||
expect(find.byType(Container), isNot(paints..scale()));
|
||||
});
|
||||
|
||||
testWidgets('DecorationImage RTL with alignment center-right and match', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: new Center(
|
||||
child: new Container(
|
||||
width: 100.0,
|
||||
height: 50.0,
|
||||
decoration: new BoxDecoration(
|
||||
image: new DecorationImage(
|
||||
image: new TestImageProvider(),
|
||||
alignment: FractionalOffset.centerRight,
|
||||
matchTextDirection: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Duration.ZERO,
|
||||
EnginePhase.layout, // so that we don't try to paint the fake images
|
||||
);
|
||||
expect(find.byType(Container), paints
|
||||
..translate(x: 50.0, y: 0.0)
|
||||
..scale(x: -1.0, y: 1.0)
|
||||
..translate(x: -50.0, y: 0.0)
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(0.0, 20.5, 16.0, 29.5))
|
||||
..restore()
|
||||
);
|
||||
expect(find.byType(Container), isNot(paints..scale()..scale()));
|
||||
expect(find.byType(Container), isNot(paints..drawImageRect()..drawImageRect()));
|
||||
});
|
||||
|
||||
testWidgets('DecorationImage RTL with alignment center-right and no match', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: new Center(
|
||||
child: new Container(
|
||||
width: 100.0,
|
||||
height: 50.0,
|
||||
decoration: new BoxDecoration(
|
||||
image: new DecorationImage(
|
||||
image: new TestImageProvider(),
|
||||
alignment: FractionalOffset.centerRight,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Duration.ZERO,
|
||||
EnginePhase.layout, // so that we don't try to paint the fake images
|
||||
);
|
||||
expect(find.byType(Container), paints
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(84.0, 20.5, 100.0, 29.5))
|
||||
);
|
||||
expect(find.byType(Container), isNot(paints..scale()));
|
||||
expect(find.byType(Container), isNot(paints..drawImageRect()..drawImageRect()));
|
||||
});
|
||||
|
||||
testWidgets('DecorationImage LTR with alignment center-right and match', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Center(
|
||||
child: new Container(
|
||||
width: 100.0,
|
||||
height: 50.0,
|
||||
decoration: new BoxDecoration(
|
||||
image: new DecorationImage(
|
||||
image: new TestImageProvider(),
|
||||
alignment: FractionalOffset.centerRight,
|
||||
matchTextDirection: true
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Duration.ZERO,
|
||||
EnginePhase.layout, // so that we don't try to paint the fake images
|
||||
);
|
||||
expect(find.byType(Container), paints
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(84.0, 20.5, 100.0, 29.5))
|
||||
);
|
||||
expect(find.byType(Container), isNot(paints..scale()));
|
||||
expect(find.byType(Container), isNot(paints..drawImageRect()..drawImageRect()));
|
||||
});
|
||||
|
||||
testWidgets('DecorationImage LTR with alignment center-right and no match', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Center(
|
||||
child: new Container(
|
||||
width: 100.0,
|
||||
height: 50.0,
|
||||
decoration: new BoxDecoration(
|
||||
image: new DecorationImage(
|
||||
image: new TestImageProvider(),
|
||||
alignment: FractionalOffset.centerRight,
|
||||
matchTextDirection: true
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Duration.ZERO,
|
||||
EnginePhase.layout, // so that we don't try to paint the fake images
|
||||
);
|
||||
expect(find.byType(Container), paints
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(84.0, 20.5, 100.0, 29.5))
|
||||
);
|
||||
expect(find.byType(Container), isNot(paints..scale()));
|
||||
expect(find.byType(Container), isNot(paints..drawImageRect()..drawImageRect()));
|
||||
});
|
||||
|
||||
testWidgets('Image RTL with alignment topEnd and match', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: new Center(
|
||||
child: new Container(
|
||||
width: 100.0,
|
||||
height: 50.0,
|
||||
child: new Image(
|
||||
image: new TestImageProvider(),
|
||||
alignment: FractionalOffsetDirectional.topEnd,
|
||||
repeat: ImageRepeat.repeatX,
|
||||
matchTextDirection: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Duration.ZERO,
|
||||
EnginePhase.layout, // so that we don't try to paint the fake images
|
||||
);
|
||||
expect(find.byType(Container), paints
|
||||
..clipRect(rect: new Rect.fromLTRB(0.0, 0.0, 100.0, 50.0))
|
||||
..translate(x: 50.0, y: 0.0)
|
||||
..scale(x: -1.0, y: 1.0)
|
||||
..translate(x: -50.0, y: 0.0)
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(-12.0, 0.0, 4.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(4.0, 0.0, 20.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(20.0, 0.0, 36.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(36.0, 0.0, 52.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(52.0, 0.0, 68.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(68.0, 0.0, 84.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(84.0, 0.0, 100.0, 9.0))
|
||||
..restore()
|
||||
);
|
||||
expect(find.byType(Container), isNot(paints..scale()..scale()));
|
||||
});
|
||||
|
||||
testWidgets('Image LTR with alignment topEnd (and pointless match)', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Center(
|
||||
child: new Container(
|
||||
width: 100.0,
|
||||
height: 50.0,
|
||||
child: new Image(
|
||||
image: new TestImageProvider(),
|
||||
alignment: FractionalOffsetDirectional.topEnd,
|
||||
repeat: ImageRepeat.repeatX,
|
||||
matchTextDirection: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Duration.ZERO,
|
||||
EnginePhase.layout, // so that we don't try to paint the fake images
|
||||
);
|
||||
expect(find.byType(Container), paints
|
||||
..clipRect(rect: new Rect.fromLTRB(0.0, 0.0, 100.0, 50.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(-12.0, 0.0, 4.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(4.0, 0.0, 20.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(20.0, 0.0, 36.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(36.0, 0.0, 52.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(52.0, 0.0, 68.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(68.0, 0.0, 84.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(84.0, 0.0, 100.0, 9.0))
|
||||
..restore()
|
||||
);
|
||||
expect(find.byType(Container), isNot(paints..scale()));
|
||||
});
|
||||
|
||||
testWidgets('Image RTL with alignment topEnd', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: new Center(
|
||||
child: new Container(
|
||||
width: 100.0,
|
||||
height: 50.0,
|
||||
child: new Image(
|
||||
image: new TestImageProvider(),
|
||||
alignment: FractionalOffsetDirectional.topEnd,
|
||||
repeat: ImageRepeat.repeatX,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Duration.ZERO,
|
||||
EnginePhase.layout, // so that we don't try to paint the fake images
|
||||
);
|
||||
expect(find.byType(Container), paints
|
||||
..clipRect(rect: new Rect.fromLTRB(0.0, 0.0, 100.0, 50.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(16.0, 0.0, 32.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(32.0, 0.0, 48.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(48.0, 0.0, 64.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(64.0, 0.0, 80.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(80.0, 0.0, 96.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(96.0, 0.0, 112.0, 9.0))
|
||||
..restore()
|
||||
);
|
||||
expect(find.byType(Container), isNot(paints..scale()));
|
||||
});
|
||||
|
||||
testWidgets('Image LTR with alignment topEnd', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Center(
|
||||
child: new Container(
|
||||
width: 100.0,
|
||||
height: 50.0,
|
||||
child: new Image(
|
||||
image: new TestImageProvider(),
|
||||
alignment: FractionalOffsetDirectional.topEnd,
|
||||
repeat: ImageRepeat.repeatX,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Duration.ZERO,
|
||||
EnginePhase.layout, // so that we don't try to paint the fake images
|
||||
);
|
||||
expect(find.byType(Container), paints
|
||||
..clipRect(rect: new Rect.fromLTRB(0.0, 0.0, 100.0, 50.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(-12.0, 0.0, 4.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(4.0, 0.0, 20.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(20.0, 0.0, 36.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(36.0, 0.0, 52.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(52.0, 0.0, 68.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(68.0, 0.0, 84.0, 9.0))
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(84.0, 0.0, 100.0, 9.0))
|
||||
..restore()
|
||||
);
|
||||
expect(find.byType(Container), isNot(paints..scale()));
|
||||
});
|
||||
|
||||
testWidgets('Image RTL with alignment center-right and match', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: new Center(
|
||||
child: new Container(
|
||||
width: 100.0,
|
||||
height: 50.0,
|
||||
child: new Image(
|
||||
image: new TestImageProvider(),
|
||||
alignment: FractionalOffset.centerRight,
|
||||
matchTextDirection: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Duration.ZERO,
|
||||
EnginePhase.layout, // so that we don't try to paint the fake images
|
||||
);
|
||||
expect(find.byType(Container), paints
|
||||
..translate(x: 50.0, y: 0.0)
|
||||
..scale(x: -1.0, y: 1.0)
|
||||
..translate(x: -50.0, y: 0.0)
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(0.0, 20.5, 16.0, 29.5))
|
||||
..restore()
|
||||
);
|
||||
expect(find.byType(Container), isNot(paints..scale()..scale()));
|
||||
expect(find.byType(Container), isNot(paints..drawImageRect()..drawImageRect()));
|
||||
});
|
||||
|
||||
testWidgets('Image RTL with alignment center-right and no match', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: new Center(
|
||||
child: new Container(
|
||||
width: 100.0,
|
||||
height: 50.0,
|
||||
child: new Image(
|
||||
image: new TestImageProvider(),
|
||||
alignment: FractionalOffset.centerRight,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Duration.ZERO,
|
||||
EnginePhase.layout, // so that we don't try to paint the fake images
|
||||
);
|
||||
expect(find.byType(Container), paints
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(84.0, 20.5, 100.0, 29.5))
|
||||
);
|
||||
expect(find.byType(Container), isNot(paints..scale()));
|
||||
expect(find.byType(Container), isNot(paints..drawImageRect()..drawImageRect()));
|
||||
});
|
||||
|
||||
testWidgets('Image LTR with alignment center-right and match', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Center(
|
||||
child: new Container(
|
||||
width: 100.0,
|
||||
height: 50.0,
|
||||
child: new Image(
|
||||
image: new TestImageProvider(),
|
||||
alignment: FractionalOffset.centerRight,
|
||||
matchTextDirection: true
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Duration.ZERO,
|
||||
EnginePhase.layout, // so that we don't try to paint the fake images
|
||||
);
|
||||
expect(find.byType(Container), paints
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(84.0, 20.5, 100.0, 29.5))
|
||||
);
|
||||
expect(find.byType(Container), isNot(paints..scale()));
|
||||
expect(find.byType(Container), isNot(paints..drawImageRect()..drawImageRect()));
|
||||
});
|
||||
|
||||
testWidgets('Image LTR with alignment center-right and no match', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Center(
|
||||
child: new Container(
|
||||
width: 100.0,
|
||||
height: 50.0,
|
||||
child: new Image(
|
||||
image: new TestImageProvider(),
|
||||
alignment: FractionalOffset.centerRight,
|
||||
matchTextDirection: true
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Duration.ZERO,
|
||||
EnginePhase.layout, // so that we don't try to paint the fake images
|
||||
);
|
||||
expect(find.byType(Container), paints
|
||||
..drawImageRect(source: new Rect.fromLTRB(0.0, 0.0, 16.0, 9.0), destination: new Rect.fromLTRB(84.0, 20.5, 100.0, 29.5))
|
||||
);
|
||||
expect(find.byType(Container), isNot(paints..scale()));
|
||||
expect(find.byType(Container), isNot(paints..drawImageRect()..drawImageRect()));
|
||||
});
|
||||
|
||||
testWidgets('Image - Switch needing direction', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Image(
|
||||
image: new TestImageProvider(),
|
||||
alignment: FractionalOffset.centerRight,
|
||||
matchTextDirection: false,
|
||||
),
|
||||
),
|
||||
Duration.ZERO,
|
||||
EnginePhase.layout, // so that we don't try to paint the fake images
|
||||
);
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Image(
|
||||
image: new TestImageProvider(),
|
||||
alignment: FractionalOffsetDirectional.centerEnd,
|
||||
matchTextDirection: true,
|
||||
),
|
||||
),
|
||||
Duration.ZERO,
|
||||
EnginePhase.layout, // so that we don't try to paint the fake images
|
||||
);
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Image(
|
||||
image: new TestImageProvider(),
|
||||
alignment: FractionalOffset.centerRight,
|
||||
matchTextDirection: false,
|
||||
),
|
||||
),
|
||||
Duration.ZERO,
|
||||
EnginePhase.layout, // so that we don't try to paint the fake images
|
||||
);
|
||||
});
|
||||
}
|
@ -21,6 +21,26 @@ void main() {
|
||||
child: child,
|
||||
));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(0.0, 0.0));
|
||||
|
||||
await tester.pumpWidget(
|
||||
const Padding(
|
||||
key: const GlobalObjectKey<Null>(null),
|
||||
padding: const EdgeInsets.only(left: 1.0),
|
||||
),
|
||||
);
|
||||
await tester.pumpWidget(const Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: const Padding(
|
||||
key: const GlobalObjectKey<Null>(null),
|
||||
padding: const EdgeInsetsDirectional.only(start: 1.0),
|
||||
),
|
||||
));
|
||||
await tester.pumpWidget(
|
||||
const Padding(
|
||||
key: const GlobalObjectKey<Null>(null),
|
||||
padding: const EdgeInsets.only(left: 1.0),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Container padding/margin RTL', (WidgetTester tester) async {
|
||||
|
@ -610,4 +610,23 @@ void main() {
|
||||
|
||||
expect(tester.getTopLeft(find.byKey(key)), const Offset(50.0, 0.0));
|
||||
});
|
||||
|
||||
testWidgets('Can change the text direction of a Stack', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new Stack(
|
||||
alignment: FractionalOffset.center,
|
||||
),
|
||||
);
|
||||
await tester.pumpWidget(
|
||||
new Stack(
|
||||
alignment: FractionalOffsetDirectional.topStart,
|
||||
textDirection: TextDirection.rtl,
|
||||
),
|
||||
);
|
||||
await tester.pumpWidget(
|
||||
new Stack(
|
||||
alignment: FractionalOffset.center,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user