Commit 882c992b authored by Adam Barth's avatar Adam Barth Committed by GitHub

Add Wrap (#9225)

The Wrap widget is a layout that places children in a run in order along its
main axis until it runs out of space. At that point, starts placing children in
a new run that is adjacent in the cross axis.

Fixes #8831
parent a559b8df
......@@ -22,13 +22,6 @@
/// initialized with those features.
library rendering;
export 'package:flutter/foundation.dart' show
VoidCallback,
ValueChanged,
ValueGetter,
ValueSetter;
export 'package:vector_math/vector_math_64.dart' show Matrix4;
export 'src/rendering/animated_size.dart';
export 'src/rendering/binding.dart';
export 'src/rendering/block.dart';
......@@ -63,3 +56,11 @@ export 'src/rendering/tweens.dart';
export 'src/rendering/view.dart';
export 'src/rendering/viewport.dart';
export 'src/rendering/viewport_offset.dart';
export 'src/rendering/wrap.dart';
export 'package:flutter/foundation.dart' show
VoidCallback,
ValueChanged,
ValueGetter,
ValueSetter;
export 'package:vector_math/vector_math_64.dart' show Matrix4;
......@@ -80,11 +80,13 @@ enum MainAxisAlignment {
/// Place the free space evenly between the children.
spaceBetween,
/// Place the free space evenly between the children as well as half of that space before and after the first and last child.
/// Place the free space evenly between the children as well as half of that
/// space before and after the first and last child.
spaceAround,
/// Place the free space evenly between the children as well as before and after the first and last child.
spaceEvenly
/// Place the free space evenly between the children as well as before and
/// after the first and last child.
spaceEvenly,
}
/// How the children should be placed along the cross axis in a flex layout.
......
......@@ -444,6 +444,13 @@ class RenderStack extends RenderBox
@override
Rect describeApproximatePaintClip(RenderObject child) => _hasVisualOverflow ? Point.origin & size : null;
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('overflow: $overflow');
description.add('alignment: $alignment');
}
}
/// Implements the same layout algorithm as RenderStack but only paints the child
......@@ -460,8 +467,8 @@ class RenderIndexedStack extends RenderStack {
FractionalOffset alignment: FractionalOffset.topLeft,
int index: 0
}) : _index = index, super(
children: children,
alignment: alignment
children: children,
alignment: alignment
);
/// The index of the child to show, null if nothing is to be displayed.
......@@ -508,4 +515,10 @@ class RenderIndexedStack extends RenderStack {
final StackParentData childParentData = child.parentData;
context.paintChild(child, childParentData.offset + offset);
}
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('index: $index');
}
}
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'box.dart';
import 'object.dart';
/// How [Wrap] should align objects.
///
/// Used both to align children within a run in the main axis as well as to
/// align the runs themselves in the cross axis.
enum WrapAlignment {
/// Place the objects as close to the start of the axis as possible.
start,
/// Place the objects as close to the end of the axis as possible.
end,
/// Place the objects as close to the middle of the axis as possible.
center,
/// Place the free space evenly between the objects.
spaceBetween,
/// Place the free space evenly between the objects as well as half of that
/// space before and after the first and last objects.
spaceAround,
/// Place the free space evenly between the objects as well as before and
/// after the first and last objects.
spaceEvenly,
}
/// Who [Wrap] should align children within a run in the cross axis.
enum WrapCrossAlignment {
/// Place the children as close to the start of the run in the cross axis as
/// possible.
start,
/// Place the children as close to the end of the run in the cross axis as
/// possible.
end,
/// Place the children as close to the middle of the run in the cross axis as
/// possible.
center,
// TODO(ianh): baseline.
}
class _RunMetrics {
_RunMetrics(this.mainAxisExtent, this.crossAxisExtent, this.childCount);
final double mainAxisExtent;
final double crossAxisExtent;
final int childCount;
}
/// Parent data for use with [RenderWrap].
class WrapParentData extends ContainerBoxParentDataMixin<RenderBox> {
int _runIndex = 0;
}
class RenderWrap extends RenderBox with ContainerRenderObjectMixin<RenderBox, WrapParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, WrapParentData> {
RenderWrap({
List<RenderBox> children,
Axis direction: Axis.horizontal,
WrapAlignment alignment: WrapAlignment.start,
double spacing: 0.0,
WrapAlignment runAlignment: WrapAlignment.start,
double runSpacing: 0.0,
WrapCrossAlignment crossAxisAlignment: WrapCrossAlignment.start,
}) : _direction = direction,
_alignment = alignment,
_spacing = spacing,
_runAlignment = runAlignment,
_runSpacing = runSpacing,
_crossAxisAlignment = crossAxisAlignment {
assert(direction != null);
assert(alignment != null);
assert(spacing != null);
assert(runAlignment != null);
assert(runSpacing != null);
assert(crossAxisAlignment != null);
addAll(children);
}
/// The direction to use as the main axis.
///
/// For example, if [direction] is [Axis.horizontal], the default, the
/// children are placed adjacent to one another in a horizontal run until the
/// available horizontal space is consumed, at which point a subsequent
/// children are placed in a new run vertically adjacent to the previous run.
Axis get direction => _direction;
Axis _direction;
set direction (Axis value) {
assert(value != null);
if (_direction == value)
return;
_direction = value;
markNeedsLayout();
}
/// How the children within a run should be places in the main axis.
///
/// For example, if [alignment] is [WrapAlignment.center], the children in
/// each run are grouped togeter in the center of their run in the main axis.
///
/// Defaults to [WrapAlignment.start].
///
/// See also:
///
/// * [runAlignment], which controls how the runs are placed relative to each
/// other in the cross axis.
/// * [crossAxisAlignment], which controls how the children within each run
/// are placed relative to each other in the cross axis.
WrapAlignment get alignment => _alignment;
WrapAlignment _alignment;
set alignment (WrapAlignment value) {
assert(value != null);
if (_alignment == value)
return;
_alignment = value;
markNeedsLayout();
}
/// How much space to place between children in a run in the main axis.
///
/// For example, if [spacing] is 10.0, the children will be spaced at least
/// 10.0 logical pixels apart in the main axis.
///
/// If there is additional free space in a run (e.g., because the wrap has a
/// minimum size that is not filled or because some runs are longer than
/// others), the additional free space will be allocated according to the
/// [alignment].
///
/// Defaults to 0.0.
double get spacing => _spacing;
double _spacing;
set spacing (double value) {
assert(value != null);
if (_spacing == value)
return;
_spacing = value;
markNeedsLayout();
}
/// How the runs themselves should be placed in the cross axis.
///
/// For example, if [runAlignment] is [WrapAlignment.center], the runs are
/// grouped togeter in the center of the overall [RenderWrap] in the cross
/// axis.
///
/// Defaults to [WrapAlignment.start].
///
/// See also:
///
/// * [alignment], which controls how the children within each run are placed
/// relative to each other in the main axis.
/// * [crossAxisAlignment], which controls how the children within each run
/// are placed relative to each other in the cross axis.
WrapAlignment get runAlignment => _runAlignment;
WrapAlignment _runAlignment;
set runAlignment (WrapAlignment value) {
assert(value != null);
if (_runAlignment == value)
return;
_runAlignment = value;
markNeedsLayout();
}
/// How much space to place between the runs themselves in the cross axis.
///
/// For example, if [runSpacing] is 10.0, the runs will be spaced at least
/// 10.0 logical pixels apart in the cross axis.
///
/// If there is additional free space in the overall [RenderWrap] (e.g.,
/// because the wrap has a minimum size that is not filled), the additional
/// free space will be allocated according to the [runAlignment].
///
/// Defaults to 0.0.
double get runSpacing => _runSpacing;
double _runSpacing;
set runSpacing (double value) {
assert(value != null);
if (_runSpacing == value)
return;
_runSpacing = value;
markNeedsLayout();
}
/// How the children within a run should be aligned relative to each other in
/// the cross axis.
///
/// For example, if this is set to [WrapCrossAlignment.end], and the
/// [direction] is [WrapDirection.horizontal], then the children within each
/// run will have their bottom edges aligned to the bottom edge of the run.
///
/// Defaults to [WrapCrossAlignment.start].
///
/// See also:
///
/// * [alignment], which controls how the children within each run are placed
/// relative to each other in the main axis.
/// * [runAlignment], which controls how the runs are placed relative to each
/// other in the cross axis.
WrapCrossAlignment get crossAxisAlignment => _crossAxisAlignment;
WrapCrossAlignment _crossAxisAlignment;
set crossAxisAlignment (WrapCrossAlignment value) {
assert(value != null);
if (_crossAxisAlignment == value)
return;
_crossAxisAlignment = value;
markNeedsLayout();
}
@override
void setupParentData(RenderBox child) {
if (child.parentData is! WrapParentData)
child.parentData = new WrapParentData();
}
double _computeIntrinsicHeightForWidth(double width) {
assert(direction == Axis.horizontal);
int runCount = 0;
double height = 0.0;
double runWidth = 0.0;
double runHeight = 0.0;
int childCount = 0;
RenderBox child = firstChild;
while (child != null) {
final double childWidth = child.getMaxIntrinsicWidth(double.INFINITY);
final double childHeight = child.getMaxIntrinsicHeight(childWidth);
if (runWidth + childWidth > width) {
height += runHeight;
if (runCount > 0)
height += runSpacing;
runCount += 1;
runWidth = 0.0;
runHeight = 0.0;
childCount = 0;
}
runWidth += childWidth;
runHeight = math.max(runHeight, childHeight);
if (childCount > 0)
runWidth += spacing;
childCount += 1;
child = childAfter(child);
}
if (childCount > 0)
height += runHeight + runSpacing;
return height;
}
double _computeIntrinsicWidthForHeight(double height) {
assert(direction == Axis.vertical);
int runCount = 0;
double width = 0.0;
double runHeight = 0.0;
double runWidth = 0.0;
int childCount = 0;
RenderBox child = firstChild;
while (child != null) {
final double childHeight = child.getMaxIntrinsicHeight(double.INFINITY);
final double childWidth = child.getMaxIntrinsicWidth(childHeight);
if (runHeight + childHeight > height) {
width += runWidth;
if (runCount > 0)
width += runSpacing;
runCount += 1;
runHeight = 0.0;
runWidth = 0.0;
childCount = 0;
}
runHeight += childHeight;
runWidth = math.max(runWidth, childWidth);
if (childCount > 0)
runHeight += spacing;
childCount += 1;
child = childAfter(child);
}
if (childCount > 0)
width += runWidth + runSpacing;
return width;
}
@override
double computeMinIntrinsicWidth(double height) {
switch (direction) {
case Axis.horizontal:
double width = 0.0;
RenderBox child = firstChild;
while (child != null) {
width = math.max(width, child.getMinIntrinsicWidth(double.INFINITY));
child = childAfter(child);
}
return width;
case Axis.vertical:
return _computeIntrinsicWidthForHeight(height);
}
return null;
}
@override
double computeMaxIntrinsicWidth(double height) {
switch (direction) {
case Axis.horizontal:
double width = 0.0;
RenderBox child = firstChild;
while (child != null) {
width += child.getMaxIntrinsicWidth(double.INFINITY);
child = childAfter(child);
}
return width;
case Axis.vertical:
return _computeIntrinsicWidthForHeight(height);
}
return null;
}
@override
double computeMinIntrinsicHeight(double width) {
switch (direction) {
case Axis.horizontal:
return _computeIntrinsicHeightForWidth(width);
case Axis.vertical:
double height = 0.0;
RenderBox child = firstChild;
while (child != null) {
height = math.max(height, child.getMinIntrinsicHeight(double.INFINITY));
child = childAfter(child);
}
return height;
}
return null;
}
@override
double computeMaxIntrinsicHeight(double width) {
switch (direction) {
case Axis.horizontal:
return _computeIntrinsicHeightForWidth(width);
case Axis.vertical:
double height = 0.0;
RenderBox child = firstChild;
while (child != null) {
height += child.getMaxIntrinsicHeight(double.INFINITY);
child = childAfter(child);
}
return height;
}
return null;
}
@override
double computeDistanceToActualBaseline(TextBaseline baseline) {
return defaultComputeDistanceToHighestActualBaseline(baseline);
}
double _getMainAxisExtent(RenderBox child) {
switch (direction) {
case Axis.horizontal:
return child.size.width;
case Axis.vertical:
return child.size.height;
}
return 0.0;
}
double _getCrossAxisExtent(RenderBox child) {
switch (direction) {
case Axis.horizontal:
return child.size.height;
case Axis.vertical:
return child.size.width;
}
return 0.0;
}
Offset _getOffset(double mainAxisOffset, double crossAxisOffset) {
switch (direction) {
case Axis.horizontal:
return new Offset(mainAxisOffset, crossAxisOffset);
case Axis.vertical:
return new Offset(crossAxisOffset, mainAxisOffset);
}
return Offset.zero;
}
double _getChildCrossAxisOffset(double runCrossAxisExtent, double childCrossAxisExtent) {
switch (crossAxisAlignment) {
case WrapCrossAlignment.start:
return 0.0;
case WrapCrossAlignment.center:
return (runCrossAxisExtent - childCrossAxisExtent) / 2.0;
case WrapCrossAlignment.end:
return (runCrossAxisExtent - childCrossAxisExtent);
}
return 0.0;
}
bool _hasVisualOverflow = false;
@override
void performLayout() {
_hasVisualOverflow = false;
RenderBox child = firstChild;
if (child == null) {
size = constraints.smallest;
return;
}
BoxConstraints childConstraints;
double mainAxisLimit = 0.0;
switch (direction) {
case Axis.horizontal:
childConstraints = new BoxConstraints(maxWidth: constraints.maxWidth);
mainAxisLimit = constraints.maxWidth;
break;
case Axis.vertical:
childConstraints = new BoxConstraints(maxHeight: constraints.maxHeight);
mainAxisLimit = constraints.maxHeight;
break;
}
assert(childConstraints != null);
assert(mainAxisLimit != null);
final List<_RunMetrics> runMetrics = <_RunMetrics>[];
double mainAxisExtent = 0.0;
double crossAxisExtent = 0.0;
double runMainAxisExtent = 0.0;
double runCrossAxisExtent = 0.0;
int childCount = 0;
while (child != null) {
child.layout(childConstraints, parentUsesSize: true);
final double childMainAxisExtent = _getMainAxisExtent(child);
final double childCrossAxisExtent = _getCrossAxisExtent(child);
if (runMainAxisExtent + childMainAxisExtent > mainAxisLimit) {
assert(childCount > 0);
mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent);
crossAxisExtent += runCrossAxisExtent;
if (runMetrics.isNotEmpty)
crossAxisExtent += runSpacing;
runMetrics.add(new _RunMetrics(runMainAxisExtent, runCrossAxisExtent, childCount));
runMainAxisExtent = 0.0;
runCrossAxisExtent = 0.0;
childCount = 0;
}
runMainAxisExtent += childMainAxisExtent;
if (childCount > 0)
runMainAxisExtent += spacing;
runCrossAxisExtent = math.max(runCrossAxisExtent, childCrossAxisExtent);
childCount += 1;
final WrapParentData childParentData = child.parentData;
childParentData._runIndex = runMetrics.length;
child = childParentData.nextSibling;
}
if (childCount > 0) {
mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent);
crossAxisExtent += runCrossAxisExtent + runSpacing;
runMetrics.add(new _RunMetrics(runMainAxisExtent, runCrossAxisExtent, childCount));
}
final int runCount = runMetrics.length;
assert(runCount > 0);
double containerMainAxisExtent = 0.0;
double containerCrossAxisExtent = 0.0;
switch (direction) {
case Axis.horizontal:
size = constraints.constrain(new Size(mainAxisExtent, crossAxisExtent));
containerMainAxisExtent = size.width;
containerCrossAxisExtent = size.height;
break;
case Axis.vertical:
size = constraints.constrain(new Size(crossAxisExtent, mainAxisExtent));
containerMainAxisExtent = size.height;
containerCrossAxisExtent = size.width;
break;
}
_hasVisualOverflow = containerMainAxisExtent < mainAxisExtent || containerCrossAxisExtent < crossAxisExtent;
final double crossAxisFreeSpace = math.max(0.0, containerCrossAxisExtent - crossAxisExtent);
double runLeadingSpace = 0.0;
double runBetweenSpace = 0.0;
switch (runAlignment) {
case WrapAlignment.start:
break;
case WrapAlignment.end:
runLeadingSpace = crossAxisFreeSpace;
break;
case WrapAlignment.center:
runLeadingSpace = crossAxisFreeSpace / 2.0;
break;
case WrapAlignment.spaceBetween:
runBetweenSpace = runCount > 1 ? crossAxisFreeSpace / (runCount - 1) : 0.0;
break;
case WrapAlignment.spaceAround:
runBetweenSpace = crossAxisFreeSpace / runCount;
runLeadingSpace = runBetweenSpace / 2.0;
break;
case WrapAlignment.spaceEvenly:
runBetweenSpace = crossAxisFreeSpace / (runCount + 1);
runLeadingSpace = runBetweenSpace;
break;
}
double crossAxisOffset = runLeadingSpace;
child = firstChild;
for (int i = 0; i < runCount; ++i) {
final _RunMetrics metrics = runMetrics[i];
final double runMainAxisExtent = metrics.mainAxisExtent;
final double runCrossAxisExtent = metrics.crossAxisExtent;
final int childCount = metrics.childCount;
final double mainAxisFreeSpace = math.max(0.0, containerMainAxisExtent - runMainAxisExtent);
double childLeadingSpace = 0.0;
double childBetweenSpace = 0.0;
switch (alignment) {
case WrapAlignment.start:
break;
case WrapAlignment.end:
childLeadingSpace = mainAxisFreeSpace;
break;
case WrapAlignment.center:
childLeadingSpace = mainAxisFreeSpace / 2.0;
break;
case WrapAlignment.spaceBetween:
childBetweenSpace = childCount > 1 ? mainAxisFreeSpace / (childCount - 1) : 0.0;
break;
case WrapAlignment.spaceAround:
childBetweenSpace = mainAxisFreeSpace / childCount;
childLeadingSpace = childBetweenSpace / 2.0;
break;
case WrapAlignment.spaceEvenly:
childBetweenSpace = mainAxisFreeSpace / (childCount + 1);
childLeadingSpace = childBetweenSpace;
break;
}
double childMainAxisOffset = childLeadingSpace;
while (child != null) {
final WrapParentData childParentData = child.parentData;
if (childParentData._runIndex != i)
break;
final double childMainAxisExtent = _getMainAxisExtent(child);
final double childCrossAxisExtent = _getCrossAxisExtent(child);
final double childCrossAxisOffset = _getChildCrossAxisOffset(runCrossAxisExtent, childCrossAxisExtent);
childParentData.offset = _getOffset(childMainAxisOffset, crossAxisOffset + childCrossAxisOffset);
childMainAxisOffset += childMainAxisExtent + spacing + childBetweenSpace;
child = childParentData.nextSibling;
}
crossAxisOffset += runCrossAxisExtent + runSpacing + runBetweenSpace;
}
}
@override
bool hitTestChildren(HitTestResult result, { Point position }) {
return defaultHitTestChildren(result, position: position);
}
@override
void paint(PaintingContext context, Offset offset) {
// TODO(ianh): move the debug flex overflow paint logic somewhere common so
// it can be reused here
if (_hasVisualOverflow)
context.pushClipRect(needsCompositing, offset, Point.origin & size, defaultPaint);
else
defaultPaint(context, offset);
}
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('direction: $direction');
description.add('alignment: $alignment');
description.add('spacing: $spacing');
description.add('runAlignment: $runAlignment');
description.add('runSpacing: $runSpacing');
description.add('crossAxisAlignment: $runSpacing');
}
}
......@@ -45,7 +45,9 @@ export 'package:flutter/rendering.dart' show
SingleChildLayoutDelegate,
TextOverflow,
ValueChanged,
ValueGetter;
ValueGetter,
WrapAlignment,
WrapCrossAlignment;
// PAINTING NODES
......@@ -2262,6 +2264,122 @@ class Expanded extends Flexible {
}) : super(key: key, flex: flex, fit: FlexFit.tight, child: child);
}
class Wrap extends MultiChildRenderObjectWidget {
Wrap({
Key key,
this.direction: Axis.horizontal,
this.alignment: WrapAlignment.start,
this.spacing: 0.0,
this.runAlignment: WrapAlignment.start,
this.runSpacing: 0.0,
this.crossAxisAlignment: WrapCrossAlignment.start,
List<Widget> children: const <Widget>[],
}) : super(key: key, children: children);
/// The direction to use as the main axis.
///
/// For example, if [direction] is [Axis.horizontal], the default, the
/// children are placed adjacent to one another in a horizontal run until the
/// available horizontal space is consumed, at which point a subsequent
/// children are placed in a new run vertically adjacent to the previous run.
final Axis direction;
/// How the children within a run should be places in the main axis.
///
/// For example, if [alignment] is [WrapAlignment.center], the children in
/// each run are grouped togeter in the center of their run in the main axis.
///
/// Defaults to [WrapAlignment.start].
///
/// See also:
///
/// * [runAlignment], which controls how the runs are placed relative to each
/// other in the cross axis.
/// * [crossAxisAlignment], which controls how the children within each run
/// are placed relative to each other in the cross axis.
final WrapAlignment alignment;
/// How much space to place between children in a run in the main axis.
///
/// For example, if [spacing] is 10.0, the children will be spaced at least
/// 10.0 logical pixels apart in the main axis.
///
/// If there is additional free space in a run (e.g., because the wrap has a
/// minimum size that is not filled or because some runs are longer than
/// others), the additional free space will be allocated according to the
/// [alignment].
///
/// Defaults to 0.0.
final double spacing;
/// How the runs themselves should be placed in the cross axis.
///
/// For example, if [runAlignment] is [WrapAlignment.center], the runs are
/// grouped togeter in the center of the overall [Wrap] in the cross axis.
///
/// Defaults to [WrapAlignment.start].
///
/// See also:
///
/// * [alignment], which controls how the children within each run are placed
/// relative to each other in the main axis.
/// * [crossAxisAlignment], which controls how the children within each run
/// are placed relative to each other in the cross axis.
final WrapAlignment runAlignment;
/// How much space to place between the runs themselves in the cross axis.
///
/// For example, if [runSpacing] is 10.0, the runs will be spaced at least
/// 10.0 logical pixels apart in the cross axis.
///
/// If there is additional free space in the overall [Wrap] (e.g., because
/// the wrap has a minimum size that is not filled), the additional free space
/// will be allocated according to the [runAlignment].
///
/// Defaults to 0.0.
final double runSpacing;
/// How the children within a run should be aligned relative to each other in
/// the cross axis.
///
/// For example, if this is set to [WrapCrossAlignment.end], and the
/// [direction] is [WrapDirection.horizontal], then the children within each
/// run will have their bottom edges aligned to the bottom edge of the run.
///
/// Defaults to [WrapCrossAlignment.start].
///
/// See also:
///
/// * [alignment], which controls how the children within each run are placed
/// relative to each other in the main axis.
/// * [runAlignment], which controls how the runs are placed relative to each
/// other in the cross axis.
final WrapCrossAlignment crossAxisAlignment;
@override
RenderWrap createRenderObject(BuildContext context) {
return new RenderWrap(
direction: direction,
alignment: alignment,
spacing: spacing,
runAlignment: runAlignment,
runSpacing: runSpacing,
crossAxisAlignment: crossAxisAlignment,
);
}
@override
void updateRenderObject(BuildContext context, RenderWrap renderObject) {
renderObject
..direction = direction
..alignment = alignment
..spacing = spacing
..runAlignment = runAlignment
..runSpacing = runSpacing
..crossAxisAlignment = crossAxisAlignment;
}
}
/// A widget that implements the flow layout algorithm.
///
/// Flow layouts are optimized for repositioning children using transformation
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import '../rendering/mock_canvas.dart';
void verify(WidgetTester tester, List<Point> answerKey) {
final List<Point> testAnswers = tester.renderObjectList<RenderBox>(find.byType(SizedBox)).map<Point>(
(RenderBox target) => target.localToGlobal(Point.origin)
).toList();
expect(testAnswers, equals(answerKey));
}
void main() {
testWidgets('Basic Wrap test', (WidgetTester tester) async {
await tester.pumpWidget(
new Wrap(
alignment: WrapAlignment.start,
children: <Widget>[
new SizedBox(width: 300.0, height: 100.0),
new SizedBox(width: 300.0, height: 100.0),
new SizedBox(width: 300.0, height: 100.0),
new SizedBox(width: 300.0, height: 100.0),
],
),
);
verify(tester, <Point>[
const Point(0.0, 0.0),
const Point(300.0, 0.0),
const Point(0.0, 100.0),
const Point(300.0, 100.0),
]);
await tester.pumpWidget(
new Wrap(
alignment: WrapAlignment.center,
children: <Widget>[
new SizedBox(width: 300.0, height: 100.0),
new SizedBox(width: 300.0, height: 100.0),
new SizedBox(width: 300.0, height: 100.0),
new SizedBox(width: 300.0, height: 100.0),
],
),
);
verify(tester, <Point>[
const Point(100.0, 0.0),
const Point(400.0, 0.0),
const Point(100.0, 100.0),
const Point(400.0, 100.0),
]);
await tester.pumpWidget(
new Wrap(
alignment: WrapAlignment.end,
children: <Widget>[
new SizedBox(width: 300.0, height: 100.0),
new SizedBox(width: 300.0, height: 100.0),
new SizedBox(width: 300.0, height: 100.0),
new SizedBox(width: 300.0, height: 100.0),
],
),
);
verify(tester, <Point>[
const Point(200.0, 0.0),
const Point(500.0, 0.0),
const Point(200.0, 100.0),
const Point(500.0, 100.0),
]);
await tester.pumpWidget(
new Wrap(
alignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.start,
children: <Widget>[
new SizedBox(width: 300.0, height: 50.0),
new SizedBox(width: 300.0, height: 100.0),
new SizedBox(width: 300.0, height: 100.0),
new SizedBox(width: 300.0, height: 50.0),
],
),
);
verify(tester, <Point>[
const Point(0.0, 0.0),
const Point(300.0, 0.0),
const Point(0.0, 100.0),
const Point(300.0, 100.0),
]);
await tester.pumpWidget(
new Wrap(
alignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.center,
children: <Widget>[
new SizedBox(width: 300.0, height: 50.0),
new SizedBox(width: 300.0, height: 100.0),
new SizedBox(width: 300.0, height: 100.0),
new SizedBox(width: 300.0, height: 50.0),
],
),
);
verify(tester, <Point>[
const Point(0.0, 25.0),
const Point(300.0, 0.0),
const Point(0.0, 100.0),
const Point(300.0, 125.0),
]);
await tester.pumpWidget(
new Wrap(
alignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.end,
children: <Widget>[
new SizedBox(width: 300.0, height: 50.0),
new SizedBox(width: 300.0, height: 100.0),
new SizedBox(width: 300.0, height: 100.0),
new SizedBox(width: 300.0, height: 50.0),
],
),
);
verify(tester, <Point>[
const Point(0.0, 50.0),
const Point(300.0, 0.0),
const Point(0.0, 100.0),
const Point(300.0, 150.0),
]);
});
testWidgets('Empty wrap', (WidgetTester tester) async {
await tester.pumpWidget(new Center(child: new Wrap()));
expect(tester.renderObject<RenderBox>(find.byType(Wrap)).size, equals(Size.zero));
});
testWidgets('Wrap alignment', (WidgetTester tester) async {
await tester.pumpWidget(new Wrap(
alignment: WrapAlignment.center,
spacing: 5.0,
children: <Widget>[
new SizedBox(width: 100.0, height: 10.0),
new SizedBox(width: 200.0, height: 20.0),
new SizedBox(width: 300.0, height: 30.0),
],
));
expect(tester.renderObject<RenderBox>(find.byType(Wrap)).size, equals(const Size(800.0, 600.0)));
verify(tester, <Point>[
const Point(95.0, 0.0),
const Point(200.0, 0.0),
const Point(405.0, 0.0),
]);
await tester.pumpWidget(new Wrap(
alignment: WrapAlignment.spaceBetween,
spacing: 5.0,
children: <Widget>[
new SizedBox(width: 100.0, height: 10.0),
new SizedBox(width: 200.0, height: 20.0),
new SizedBox(width: 300.0, height: 30.0),
],
));
expect(tester.renderObject<RenderBox>(find.byType(Wrap)).size, equals(const Size(800.0, 600.0)));
verify(tester, <Point>[
const Point(0.0, 0.0),
const Point(200.0, 0.0),
const Point(500.0, 0.0),
]);
await tester.pumpWidget(new Wrap(
alignment: WrapAlignment.spaceAround,
spacing: 5.0,
children: <Widget>[
new SizedBox(width: 100.0, height: 10.0),
new SizedBox(width: 200.0, height: 20.0),
new SizedBox(width: 310.0, height: 30.0),
],
));
expect(tester.renderObject<RenderBox>(find.byType(Wrap)).size, equals(const Size(800.0, 600.0)));
verify(tester, <Point>[
const Point(30.0, 0.0),
const Point(195.0, 0.0),
const Point(460.0, 0.0),
]);
await tester.pumpWidget(new Wrap(
alignment: WrapAlignment.spaceEvenly,
spacing: 5.0,
children: <Widget>[
new SizedBox(width: 100.0, height: 10.0),
new SizedBox(width: 200.0, height: 20.0),
new SizedBox(width: 310.0, height: 30.0),
],
));
expect(tester.renderObject<RenderBox>(find.byType(Wrap)).size, equals(const Size(800.0, 600.0)));
verify(tester, <Point>[
const Point(45.0, 0.0),
const Point(195.0, 0.0),
const Point(445.0, 0.0),
]);
});
testWidgets('Wrap runAlignment', (WidgetTester tester) async {
await tester.pumpWidget(new Wrap(
runAlignment: WrapAlignment.center,
runSpacing: 5.0,
children: <Widget>[
new SizedBox(width: 100.0, height: 10.0),
new SizedBox(width: 200.0, height: 20.0),
new SizedBox(width: 300.0, height: 30.0),
new SizedBox(width: 400.0, height: 40.0),
new SizedBox(width: 500.0, height: 60.0),
],
));
expect(tester.renderObject<RenderBox>(find.byType(Wrap)).size, equals(const Size(800.0, 600.0)));
verify(tester, <Point>[
const Point(0.0, 230.0),
const Point(100.0, 230.0),
const Point(300.0, 230.0),
const Point(0.0, 265.0),
const Point(0.0, 310.0),
]);
await tester.pumpWidget(new Wrap(
runAlignment: WrapAlignment.spaceBetween,
runSpacing: 5.0,
children: <Widget>[
new SizedBox(width: 100.0, height: 10.0),
new SizedBox(width: 200.0, height: 20.0),
new SizedBox(width: 300.0, height: 30.0),
new SizedBox(width: 400.0, height: 40.0),
new SizedBox(width: 500.0, height: 60.0),
],
));
expect(tester.renderObject<RenderBox>(find.byType(Wrap)).size, equals(const Size(800.0, 600.0)));
verify(tester, <Point>[
const Point(0.0, 0.0),
const Point(100.0, 0.0),
const Point(300.0, 0.0),
const Point(0.0, 265.0),
const Point(0.0, 540.0),
]);
await tester.pumpWidget(new Wrap(
runAlignment: WrapAlignment.spaceAround,
runSpacing: 5.0,
children: <Widget>[
new SizedBox(width: 100.0, height: 10.0),
new SizedBox(width: 200.0, height: 20.0),
new SizedBox(width: 300.0, height: 30.0),
new SizedBox(width: 400.0, height: 40.0),
new SizedBox(width: 500.0, height: 70.0),
],
));
expect(tester.renderObject<RenderBox>(find.byType(Wrap)).size, equals(const Size(800.0, 600.0)));
verify(tester, <Point>[
const Point(0.0, 75.0),
const Point(100.0, 75.0),
const Point(300.0, 75.0),
const Point(0.0, 260.0),
const Point(0.0, 455.0),
]);
await tester.pumpWidget(new Wrap(
runAlignment: WrapAlignment.spaceEvenly,
runSpacing: 5.0,
children: <Widget>[
new SizedBox(width: 100.0, height: 10.0),
new SizedBox(width: 200.0, height: 20.0),
new SizedBox(width: 300.0, height: 30.0),
new SizedBox(width: 400.0, height: 40.0),
new SizedBox(width: 500.0, height: 60.0),
],
));
expect(tester.renderObject<RenderBox>(find.byType(Wrap)).size, equals(const Size(800.0, 600.0)));
verify(tester, <Point>[
const Point(0.0, 115.0),
const Point(100.0, 115.0),
const Point(300.0, 115.0),
const Point(0.0, 265.0),
const Point(0.0, 425.0),
]);
});
testWidgets('Shrink-wrapping Wrap test', (WidgetTester tester) async {
await tester.pumpWidget(
new Align(
alignment: FractionalOffset.topLeft,
child: new Wrap(
alignment: WrapAlignment.end,
crossAxisAlignment: WrapCrossAlignment.end,
children: <Widget>[
new SizedBox(width: 100.0, height: 10.0),
new SizedBox(width: 200.0, height: 20.0),
new SizedBox(width: 300.0, height: 30.0),
new SizedBox(width: 400.0, height: 40.0),
],
),
),
);
expect(tester.renderObject<RenderBox>(find.byType(Wrap)).size, equals(const Size(600.0, 70.0)));
verify(tester, <Point>[
const Point(0.0, 20.0),
const Point(100.0, 10.0),
const Point(300.0, 0.0),
const Point(200.0, 30.0),
]);
await tester.pumpWidget(
new Align(
alignment: FractionalOffset.topLeft,
child: new Wrap(
alignment: WrapAlignment.end,
crossAxisAlignment: WrapCrossAlignment.end,
children: <Widget>[
new SizedBox(width: 400.0, height: 40.0),
new SizedBox(width: 300.0, height: 30.0),
new SizedBox(width: 200.0, height: 20.0),
new SizedBox(width: 100.0, height: 10.0),
],
),
),
);
expect(tester.renderObject<RenderBox>(find.byType(Wrap)).size, equals(const Size(700.0, 60.0)));
verify(tester, <Point>[
const Point(0.0, 0.0),
const Point(400.0, 10.0),
const Point(400.0, 40.0),
const Point(600.0, 50.0),
]);
});
testWidgets('Wrap spacing test', (WidgetTester tester) async {
await tester.pumpWidget(
new Align(
alignment: FractionalOffset.topLeft,
child: new Wrap(
runSpacing: 10.0,
alignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.start,
children: <Widget>[
new SizedBox(width: 500.0, height: 10.0),
new SizedBox(width: 500.0, height: 20.0),
new SizedBox(width: 500.0, height: 30.0),
new SizedBox(width: 500.0, height: 40.0),
],
),
),
);
expect(tester.renderObject<RenderBox>(find.byType(Wrap)).size, equals(const Size(500.0, 130.0)));
verify(tester, <Point>[
const Point(0.0, 0.0),
const Point(0.0, 20.0),
const Point(0.0, 50.0),
const Point(0.0, 90.0),
]);
});
testWidgets('Vertical Wrap test with spacing', (WidgetTester tester) async {
await tester.pumpWidget(
new Align(
alignment: FractionalOffset.topLeft,
child: new Wrap(
direction: Axis.vertical,
spacing: 10.0,
runSpacing: 15.0,
alignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.start,
children: <Widget>[
new SizedBox(width: 10.0, height: 250.0),
new SizedBox(width: 20.0, height: 250.0),
new SizedBox(width: 30.0, height: 250.0),
new SizedBox(width: 40.0, height: 250.0),
new SizedBox(width: 50.0, height: 250.0),
new SizedBox(width: 60.0, height: 250.0),
],
),
),
);
expect(tester.renderObject<RenderBox>(find.byType(Wrap)).size, equals(const Size(150.0, 510.0)));
verify(tester, <Point>[
const Point(0.0, 0.0),
const Point(0.0, 260.0),
const Point(35.0, 0.0),
const Point(35.0, 260.0),
const Point(90.0, 0.0),
const Point(90.0, 260.0),
]);
await tester.pumpWidget(
new Align(
alignment: FractionalOffset.topLeft,
child: new Wrap(
direction: Axis.horizontal,
spacing: 12.0,
runSpacing: 8.0,
children: <Widget>[
new SizedBox(width: 10.0, height: 250.0),
new SizedBox(width: 20.0, height: 250.0),
new SizedBox(width: 30.0, height: 250.0),
new SizedBox(width: 40.0, height: 250.0),
new SizedBox(width: 50.0, height: 250.0),
new SizedBox(width: 60.0, height: 250.0),
],
),
),
);
expect(tester.renderObject<RenderBox>(find.byType(Wrap)).size, equals(const Size(270.0, 258.0)));
verify(tester, <Point>[
const Point(0.0, 0.0),
const Point(22.0, 0.0),
const Point(54.0, 0.0),
const Point(96.0, 0.0),
const Point(148.0, 0.0),
const Point(210.0, 0.0),
]);
});
testWidgets('Visual overflow generates a clip', (WidgetTester tester) async {
await tester.pumpWidget(new Wrap(
children: <Widget>[
new SizedBox(width: 500.0, height: 500.0),
],
));
expect(tester.renderObject<RenderBox>(find.byType(Wrap)), isNot(paints..clipRect()));
await tester.pumpWidget(new Wrap(
children: <Widget>[
new SizedBox(width: 500.0, height: 500.0),
new SizedBox(width: 500.0, height: 500.0),
],
));
expect(tester.renderObject<RenderBox>(find.byType(Wrap)), paints..clipRect());
});
testWidgets('Hit test children in wrap', (WidgetTester tester) async {
final List<String> log = <String>[];
await tester.pumpWidget(new Wrap(
spacing: 10.0,
runSpacing: 15.0,
children: <Widget>[
new SizedBox(width: 200.0, height: 300.0),
new SizedBox(width: 200.0, height: 300.0),
new SizedBox(width: 200.0, height: 300.0),
new SizedBox(width: 200.0, height: 300.0),
new SizedBox(
width: 200.0,
height: 300.0,
child: new GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () { log.add('hit'); },
),
),
],
));
await tester.tapAt(const Point(209.0, 314.0));
expect(log, isEmpty);
await tester.tapAt(const Point(211.0, 314.0));
expect(log, isEmpty);
await tester.tapAt(const Point(209.0, 316.0));
expect(log, isEmpty);
await tester.tapAt(const Point(211.0, 316.0));
expect(log, equals(<String>['hit']));
});
testWidgets('RenderWrap toStringShallow control test', (WidgetTester tester) async {
await tester.pumpWidget(new Wrap());
final RenderBox wrap = tester.renderObject(find.byType(Wrap));
expect(wrap.toStringShallow(), hasOneLineDescription);
});
testWidgets('RenderWrap toString control test', (WidgetTester tester) async {
await tester.pumpWidget(new Wrap(
direction: Axis.vertical,
runSpacing: 7.0,
children: <Widget>[
new SizedBox(width: 500.0, height: 400.0),
new SizedBox(width: 500.0, height: 400.0),
new SizedBox(width: 500.0, height: 400.0),
new SizedBox(width: 500.0, height: 400.0),
],
));
final RenderBox wrap = tester.renderObject(find.byType(Wrap));
final double width = wrap.getMinIntrinsicWidth(600.0);
expect(width, equals(2021));
});
testWidgets('Wrap baseline control test', (WidgetTester tester) async {
await tester.pumpWidget(
new Center(
child: new Baseline(
baseline: 180.0,
baselineType: TextBaseline.alphabetic,
child: new DefaultTextStyle(
style: const TextStyle(
fontFamily: 'Ahem',
fontSize: 100.0,
),
child: new Wrap(
children: <Widget>[
new Text('X'),
],
),
),
),
),
);
expect(tester.renderObject<RenderBox>(find.text('X')).size, const Size(100.0, 100.0));
expect(tester.renderObject<RenderBox>(find.byType(Baseline)).size, const Size(100.0, 200.0));
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment