Commit a30b109c authored by Adam Barth's avatar Adam Barth Committed by GitHub

Add RTL support to ListBody (#12414)

Fixes #11930
parent cd3715a8
......@@ -32,7 +32,6 @@ 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';
export 'src/rendering/box.dart';
export 'src/rendering/custom_layout.dart';
export 'src/rendering/debug.dart';
......@@ -42,6 +41,7 @@ export 'src/rendering/flex.dart';
export 'src/rendering/flow.dart';
export 'src/rendering/image.dart';
export 'src/rendering/layer.dart';
export 'src/rendering/list_body.dart';
export 'src/rendering/node.dart';
export 'src/rendering/object.dart';
export 'src/rendering/paragraph.dart';
......
......@@ -649,11 +649,15 @@ class _MergeableMaterialListBody extends ListBody {
final List<MergeableMaterialItem> items;
final List<BoxShadow> boxShadows;
AxisDirection _getDirection(BuildContext context) {
return getAxisDirectionFromAxisReverseAndDirectionality(context, mainAxis, false);
}
@override
RenderListBody createRenderObject(BuildContext context) {
return new _RenderMergeableMaterialListBody(
mainAxis: mainAxis,
boxShadows: boxShadows
axisDirection: _getDirection(context),
boxShadows: boxShadows,
);
}
......@@ -661,7 +665,7 @@ class _MergeableMaterialListBody extends ListBody {
void updateRenderObject(BuildContext context, RenderListBody renderObject) {
final _RenderMergeableMaterialListBody materialRenderListBody = renderObject;
materialRenderListBody
..mainAxis = mainAxis
..axisDirection = _getDirection(context)
..boxShadows = boxShadows;
}
}
......@@ -669,9 +673,9 @@ class _MergeableMaterialListBody extends ListBody {
class _RenderMergeableMaterialListBody extends RenderListBody {
_RenderMergeableMaterialListBody({
List<RenderBox> children,
Axis mainAxis: Axis.vertical,
AxisDirection axisDirection: AxisDirection.down,
this.boxShadows
}) : super(children: children, mainAxis: mainAxis);
}) : super(children: children, axisDirection: axisDirection);
List<BoxShadow> boxShadows;
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' show TextDirection;
export 'dart:ui' show
BlendMode,
BlurStyle,
......@@ -157,3 +159,108 @@ enum VerticalDirection {
/// The "start" is at the top, the "end" is at the bottom.
down,
}
/// A direction along either the horizontal or vertical [Axis].
enum AxisDirection {
/// Zero is at the bottom and positive values are above it: ⇈
///
/// Alphabetical content with a [GrowthDirection.forward] would have the A at
/// the bottom and the Z at the top. This is an unusual configuration.
up,
/// Zero is on the left and positive values are to the right of it: ⇉
///
/// Alphabetical content with a [GrowthDirection.forward] would have the A on
/// the left and the Z on the right. This is the ordinary reading order for a
/// horizontal set of tabs in an English application, for example.
right,
/// Zero is at the top and positive values are below it: ⇊
///
/// Alphabetical content with a [GrowthDirection.forward] would have the A at
/// the top and the Z at the bottom. This is the ordinary reading order for a
/// vertical list.
down,
/// Zero is to the right and positive values are to the left of it: ⇇
///
/// Alphabetical content with a [GrowthDirection.forward] would have the A at
/// the right and the Z at the left. This is the ordinary reading order for a
/// horizontal set of tabs in a Hebrew application, for example.
left,
}
/// Returns the [Axis] that contains the given [AxisDirection].
///
/// Specifically, returns [Axis.vertical] for [AxisDirection.up] and
/// [AxisDirection.down] and returns [Axis.horizontal] for [AxisDirection.left]
/// and [AxisDirection.right].
Axis axisDirectionToAxis(AxisDirection axisDirection) {
assert(axisDirection != null);
switch (axisDirection) {
case AxisDirection.up:
case AxisDirection.down:
return Axis.vertical;
case AxisDirection.left:
case AxisDirection.right:
return Axis.horizontal;
}
return null;
}
/// Returns the [AxisDirection] in which reading occurs in the given [TextDirection].
///
/// Specifically, returns [AxisDirection.left] for [TextDirection.rtl] and
/// [AxisDirection.right] for [TextDirection.ltr].
AxisDirection textDirectionToAxisDirection(TextDirection textDirection) {
assert(textDirection != null);
switch (textDirection) {
case TextDirection.rtl:
return AxisDirection.left;
case TextDirection.ltr:
return AxisDirection.right;
}
return null;
}
/// Returns the opposite of the given [AxisDirection].
///
/// Specifically, returns [AxisDirection.up] for [AxisDirection.down] (and
/// vice versa), as well as [AxisDirection.left] for [AxisDirection.right] (and
/// vice versa).
///
/// See also:
///
/// * [flipAxis], which does the same thing for [Axis] values.
AxisDirection flipAxisDirection(AxisDirection axisDirection) {
assert(axisDirection != null);
switch (axisDirection) {
case AxisDirection.up:
return AxisDirection.down;
case AxisDirection.right:
return AxisDirection.left;
case AxisDirection.down:
return AxisDirection.up;
case AxisDirection.left:
return AxisDirection.right;
}
return null;
}
/// Returns whether travelling along the given axis direction visits coordinates
/// along that axis in numerically decreasing order.
///
/// Specifically, returns true for [AxisDirection.up] and [AxisDirection.left]
/// and false for [AxisDirection.down] for [AxisDirection.right].
bool axisDirectionIsReversed(AxisDirection axisDirection) {
assert(axisDirection != null);
switch (axisDirection) {
case AxisDirection.up:
case AxisDirection.left:
return true;
case AxisDirection.down:
case AxisDirection.right:
return false;
}
return null;
}
......@@ -31,8 +31,9 @@ class RenderListBody extends RenderBox
/// By default, children are arranged along the vertical axis.
RenderListBody({
List<RenderBox> children,
Axis mainAxis: Axis.vertical,
}) : _mainAxis = mainAxis {
AxisDirection axisDirection: AxisDirection.down,
}) : assert(axisDirection != null),
_axisDirection = axisDirection {
addAll(children);
}
......@@ -42,41 +43,17 @@ class RenderListBody extends RenderBox
child.parentData = new ListBodyParentData();
}
/// The direction to use as the main axis.
Axis get mainAxis => _mainAxis;
Axis _mainAxis;
set mainAxis(Axis value) {
if (_mainAxis != value) {
_mainAxis = value;
markNeedsLayout();
}
}
BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
assert(_mainAxis != null);
switch (_mainAxis) {
case Axis.horizontal:
return new BoxConstraints.tightFor(height: constraints.maxHeight);
case Axis.vertical:
return new BoxConstraints.tightFor(width: constraints.maxWidth);
}
return null;
AxisDirection get axisDirection => _axisDirection;
AxisDirection _axisDirection;
set axisDirection(AxisDirection value) {
assert(value != null);
if (_axisDirection == value)
return;
_axisDirection = value;
markNeedsLayout();
}
double get _mainAxisExtent {
final RenderBox child = lastChild;
if (child == null)
return 0.0;
final BoxParentData parentData = child.parentData;
assert(mainAxis != null);
switch (mainAxis) {
case Axis.horizontal:
return parentData.offset.dx + child.size.width;
case Axis.vertical:
return parentData.offset.dy + child.size.height;
}
return null;
}
Axis get mainAxis => axisDirectionToAxis(axisDirection);
@override
void performLayout() {
......@@ -124,41 +101,81 @@ class RenderListBody extends RenderBox
'This is relatively expensive, however.' // (that's why we don't do it automatically)
);
}());
final BoxConstraints innerConstraints = _getInnerConstraints(constraints);
double position = 0.0;
double mainAxisExtent = 0.0;
RenderBox child = firstChild;
while (child != null) {
child.layout(innerConstraints, parentUsesSize: true);
final ListBodyParentData childParentData = child.parentData;
switch (mainAxis) {
case Axis.horizontal:
childParentData.offset = new Offset(position, 0.0);
position += child.size.width;
break;
case Axis.vertical:
childParentData.offset = new Offset(0.0, position);
position += child.size.height;
break;
switch (axisDirection) {
case AxisDirection.right:
final BoxConstraints innerConstraints = new BoxConstraints.tightFor(height: constraints.maxHeight);
while (child != null) {
child.layout(innerConstraints, parentUsesSize: true);
final ListBodyParentData childParentData = child.parentData;
childParentData.offset = new Offset(mainAxisExtent, 0.0);
mainAxisExtent += child.size.width;
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
switch (mainAxis) {
case Axis.horizontal:
size = constraints.constrain(new Size(_mainAxisExtent, constraints.maxHeight));
break;
case Axis.vertical:
size = constraints.constrain(new Size(constraints.maxWidth, _mainAxisExtent));
break;
size = constraints.constrain(new Size(mainAxisExtent, constraints.maxHeight));
break;
case AxisDirection.left:
final BoxConstraints innerConstraints = new BoxConstraints.tightFor(height: constraints.maxHeight);
while (child != null) {
child.layout(innerConstraints, parentUsesSize: true);
final ListBodyParentData childParentData = child.parentData;
mainAxisExtent += child.size.width;
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
double position = 0.0;
child = firstChild;
while (child != null) {
final ListBodyParentData childParentData = child.parentData;
position += child.size.width;
childParentData.offset = new Offset(mainAxisExtent - position, 0.0);
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
size = constraints.constrain(new Size(mainAxisExtent, constraints.maxHeight));
break;
case AxisDirection.down:
final BoxConstraints innerConstraints = new BoxConstraints.tightFor(width: constraints.maxWidth);
while (child != null) {
child.layout(innerConstraints, parentUsesSize: true);
final ListBodyParentData childParentData = child.parentData;
childParentData.offset = new Offset(0.0, mainAxisExtent);
mainAxisExtent += child.size.height;
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
size = constraints.constrain(new Size(constraints.maxWidth, mainAxisExtent));
break;
case AxisDirection.up:
final BoxConstraints innerConstraints = new BoxConstraints.tightFor(width: constraints.maxWidth);
while (child != null) {
child.layout(innerConstraints, parentUsesSize: true);
final ListBodyParentData childParentData = child.parentData;
mainAxisExtent += child.size.height;
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
double position = 0.0;
child = firstChild;
while (child != null) {
final ListBodyParentData childParentData = child.parentData;
position += child.size.height;
childParentData.offset = new Offset(0.0, mainAxisExtent - position);
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
size = constraints.constrain(new Size(constraints.maxWidth, mainAxisExtent));
break;
}
assert(size.isFinite);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
description.add(new EnumProperty<Axis>('mainAxis', mainAxis));
description.add(new EnumProperty<AxisDirection>('axisDirection', axisDirection));
}
double _getIntrinsicCrossAxis(_ChildSizingFunction childSize) {
......
......@@ -41,111 +41,6 @@ enum GrowthDirection {
reverse,
}
/// A direction along either the horizontal or vertical [Axis].
enum AxisDirection {
/// Zero is at the bottom and positive values are above it: ⇈
///
/// Alphabetical content with a [GrowthDirection.forward] would have the A at
/// the bottom and the Z at the top. This is an unusual configuration.
up,
/// Zero is on the left and positive values are to the right of it: ⇉
///
/// Alphabetical content with a [GrowthDirection.forward] would have the A on
/// the left and the Z on the right. This is the ordinary reading order for a
/// horizontal set of tabs in an English application, for example.
right,
/// Zero is at the top and positive values are below it: ⇊
///
/// Alphabetical content with a [GrowthDirection.forward] would have the A at
/// the top and the Z at the bottom. This is the ordinary reading order for a
/// vertical list.
down,
/// Zero is to the right and positive values are to the left of it: ⇇
///
/// Alphabetical content with a [GrowthDirection.forward] would have the A at
/// the right and the Z at the left. This is the ordinary reading order for a
/// horizontal set of tabs in a Hebrew application, for example.
left,
}
/// Returns the [Axis] that contains the given [AxisDirection].
///
/// Specifically, returns [Axis.vertical] for [AxisDirection.up] and
/// [AxisDirection.down] and returns [Axis.horizontal] for [AxisDirection.left]
/// and [AxisDirection.right].
Axis axisDirectionToAxis(AxisDirection axisDirection) {
assert(axisDirection != null);
switch (axisDirection) {
case AxisDirection.up:
case AxisDirection.down:
return Axis.vertical;
case AxisDirection.left:
case AxisDirection.right:
return Axis.horizontal;
}
return null;
}
/// Returns the [AxisDirection] in which reading occurs in the given [TextDirection].
///
/// Specifically, returns [AxisDirection.left] for [TextDirection.rtl] and
/// [AxisDirection.right] for [TextDirection.ltr].
AxisDirection textDirectionToAxisDirection(TextDirection textDirection) {
assert(textDirection != null);
switch (textDirection) {
case TextDirection.rtl:
return AxisDirection.left;
case TextDirection.ltr:
return AxisDirection.right;
}
return null;
}
/// Returns the opposite of the given [AxisDirection].
///
/// Specifically, returns [AxisDirection.up] for [AxisDirection.down] (and
/// vice versa), as well as [AxisDirection.left] for [AxisDirection.right] (and
/// vice versa).
///
/// See also:
///
/// * [flipAxis], which does the same thing for [Axis] values.
AxisDirection flipAxisDirection(AxisDirection axisDirection) {
assert(axisDirection != null);
switch (axisDirection) {
case AxisDirection.up:
return AxisDirection.down;
case AxisDirection.right:
return AxisDirection.left;
case AxisDirection.down:
return AxisDirection.up;
case AxisDirection.left:
return AxisDirection.right;
}
return null;
}
/// Returns whether travelling along the given axis direction visits coordinates
/// along that axis in numerically decreasing order.
///
/// Specifically, returns true for [AxisDirection.up] and [AxisDirection.left]
/// and false for [AxisDirection.down] for [AxisDirection.right].
bool axisDirectionIsReversed(AxisDirection axisDirection) {
assert(axisDirection != null);
switch (axisDirection) {
case AxisDirection.up:
case AxisDirection.left:
return true;
case AxisDirection.down:
case AxisDirection.right:
return false;
}
return null;
}
/// Flips the [AxisDirection] if the [GrowthDirection] is [GrowthDirection.reverse].
///
/// Specifically, returns `axisDirection` if `growthDirection` is
......
......@@ -2116,6 +2116,42 @@ class SliverPadding extends SingleChildRenderObjectWidget {
// LAYOUT NODES
/// Returns the [AxisDirection] in the given [Axis] in the current
/// [Directionality] (or the reverse if `reverse` is true).
///
/// If `axis` is [Axis.vertical], this function returns [AxisDirection.down]
/// unless `reverse` is true, in which case this function returns
/// [AxisDirection.up].
///
/// If `axis` is [Axis.horizontal], this function checks the current
/// [Directionality]. If the current [Directionality] is right-to-left, then
/// this function returns [AxisDirection.left] (unless `reverse` is true, in
/// which case it returns [AxisDirection.right]). Similarly, if the current
/// [Directionality] is left-to-right, then this function returns
/// [AxisDirection.right] (unless `reverse` is true, in which case it returns
/// [AxisDirection.left]).
///
/// This function is used by a number of scrolling widgets (e.g., [ListView],
/// [GridView], [PageView], and [SingleChildScrollView]) as well as [ListBody]
/// to translate their [Axis] and `reverse` properties into a concrete
/// [AxisDirection].
AxisDirection getAxisDirectionFromAxisReverseAndDirectionality(
BuildContext context,
Axis axis,
bool reverse,
) {
switch (axis) {
case Axis.horizontal:
assert(debugCheckHasDirectionality(context));
final TextDirection textDirection = Directionality.of(context);
final AxisDirection axisDirection = textDirectionToAxisDirection(textDirection);
return reverse ? flipAxisDirection(axisDirection) : axisDirection;
case Axis.vertical:
return reverse ? AxisDirection.up : AxisDirection.down;
}
return null;
}
/// A widget that arranges its children sequentially along a given axis, forcing
/// them to the dimension of the parent in the other axis.
///
......@@ -2142,6 +2178,7 @@ class ListBody extends MultiChildRenderObjectWidget {
ListBody({
Key key,
this.mainAxis: Axis.vertical,
this.reverse: false,
List<Widget> children: const <Widget>[],
}) : assert(mainAxis != null),
super(key: key, children: children);
......@@ -2149,12 +2186,32 @@ class ListBody extends MultiChildRenderObjectWidget {
/// The direction to use as the main axis.
final Axis mainAxis;
/// Whether the list body positions children in the reading direction.
///
/// For example, if the reading direction is left-to-right and
/// [mainAxis] is [Axis.horizontal], then the list body positions children
/// from left to right when [reverse] is false and from right to left when
/// [reverse] is true.
///
/// Similarly, if [mainAxis] is [Axis.vertical], then the list body positions
/// from top to bottom when [reverse] is false and from bottom to top when
/// [reverse] is true.
///
/// Defaults to false.
final bool reverse;
AxisDirection _getDirection(BuildContext context) {
return getAxisDirectionFromAxisReverseAndDirectionality(context, mainAxis, reverse);
}
@override
RenderListBody createRenderObject(BuildContext context) => new RenderListBody(mainAxis: mainAxis);
RenderListBody createRenderObject(BuildContext context) {
return new RenderListBody(axisDirection: _getDirection(context));
}
@override
void updateRenderObject(BuildContext context, RenderListBody renderObject) {
renderObject.mainAxis = mainAxis;
renderObject.axisDirection = _getDirection(context);
}
}
......
......@@ -6,7 +6,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'basic.dart';
import 'debug.dart';
import 'framework.dart';
import 'primary_scroll_controller.dart';
import 'scroll_controller.dart';
......@@ -179,16 +178,7 @@ abstract class ScrollView extends StatelessWidget {
/// [AxisDirection.right].
@protected
AxisDirection getDirection(BuildContext context) {
switch (scrollDirection) {
case Axis.horizontal:
assert(debugCheckHasDirectionality(context));
final TextDirection textDirection = Directionality.of(context);
final AxisDirection axisDirection = textDirectionToAxisDirection(textDirection);
return reverse ? flipAxisDirection(axisDirection) : axisDirection;
case Axis.vertical:
return reverse ? AxisDirection.up : AxisDirection.down;
}
return null;
return getAxisDirectionFromAxisReverseAndDirectionality(context, scrollDirection, reverse);
}
/// Subclasses should override this method to build the slivers for the inside
......
......@@ -8,7 +8,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'basic.dart';
import 'debug.dart';
import 'framework.dart';
import 'primary_scroll_controller.dart';
import 'scroll_controller.dart';
......@@ -70,7 +69,7 @@ class SingleChildScrollView extends StatelessWidget {
/// left to right when [reverse] is false and from right to left when
/// [reverse] is true.
///
/// Similarly, if [scrollDirection] is [Axis.vertical], then scroll view
/// Similarly, if [scrollDirection] is [Axis.vertical], then the scroll view
/// scrolls from top to bottom when [reverse] is false and from bottom to top
/// when [reverse] is true.
///
......@@ -116,16 +115,7 @@ class SingleChildScrollView extends StatelessWidget {
final Widget child;
AxisDirection _getDirection(BuildContext context) {
switch (scrollDirection) {
case Axis.horizontal:
assert(debugCheckHasDirectionality(context));
final TextDirection textDirection = Directionality.of(context);
final AxisDirection axisDirection = textDirectionToAxisDirection(textDirection);
return reverse ? flipAxisDirection(axisDirection) : axisDirection;
case Axis.vertical:
return reverse ? AxisDirection.up : AxisDirection.down;
}
return null;
return getAxisDirectionFromAxisReverseAndDirectionality(context, scrollDirection, reverse);
}
@override
......
......@@ -17,7 +17,7 @@ void main() {
final RenderListBody testBlock = new RenderListBody(
children: <RenderBox>[
paragraph,
]
],
);
final double textWidth = paragraph.getMaxIntrinsicWidth(double.INFINITY);
......@@ -52,7 +52,7 @@ void main() {
expect(testBlock.getMaxIntrinsicHeight(0.0), equals(manyLinesTextHeight));
// horizontal block (same expectations again)
testBlock.mainAxis = Axis.horizontal;
testBlock.axisDirection = AxisDirection.right;
expect(testBlock.getMinIntrinsicWidth(double.INFINITY), equals(wrappedTextWidth));
expect(testBlock.getMaxIntrinsicWidth(double.INFINITY), equals(textWidth));
expect(testBlock.getMinIntrinsicHeight(double.INFINITY), equals(oneLineTextHeight));
......
// 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/widgets.dart';
final List<Widget> children = <Widget>[
new Container(width: 200.0, height: 150.0),
new Container(width: 200.0, height: 150.0),
new Container(width: 200.0, height: 150.0),
new Container(width: 200.0, height: 150.0),
];
void expectRects(WidgetTester tester, List<Rect> expected) {
final Finder finder = find.byType(Container);
finder.precache();
final List<Rect> actual = <Rect>[];
for (int i = 0; i < expected.length; ++i) {
final Finder current = finder.at(i);
expect(current, findsOneWidget);
actual.add(tester.getRect(finder.at(i)));
}
expect(() => finder.at(expected.length), throwsRangeError);
expect(actual, equals(expected));
}
void main() {
testWidgets('ListBody down', (WidgetTester tester) async {
await tester.pumpWidget(new Flex(
direction: Axis.vertical,
children: <Widget>[ new ListBody(children: children) ],
));
expectRects(
tester,
<Rect>[
new Rect.fromLTWH(0.0, 0.0, 800.0, 150.0),
new Rect.fromLTWH(0.0, 150.0, 800.0, 150.0),
new Rect.fromLTWH(0.0, 300.0, 800.0, 150.0),
new Rect.fromLTWH(0.0, 450.0, 800.0, 150.0),
],
);
});
testWidgets('ListBody up', (WidgetTester tester) async {
await tester.pumpWidget(new Flex(
direction: Axis.vertical,
children: <Widget>[ new ListBody(reverse: true, children: children) ],
));
expectRects(
tester,
<Rect>[
new Rect.fromLTWH(0.0, 450.0, 800.0, 150.0),
new Rect.fromLTWH(0.0, 300.0, 800.0, 150.0),
new Rect.fromLTWH(0.0, 150.0, 800.0, 150.0),
new Rect.fromLTWH(0.0, 0.0, 800.0, 150.0),
],
);
});
testWidgets('ListBody right', (WidgetTester tester) async {
await tester.pumpWidget(new Flex(
textDirection: TextDirection.ltr,
direction: Axis.horizontal,
children: <Widget>[
new Directionality(
textDirection: TextDirection.ltr,
child: new ListBody(mainAxis: Axis.horizontal, children: children),
),
],
));
expectRects(
tester,
<Rect>[
new Rect.fromLTWH(0.0, 0.0, 200.0, 600.0),
new Rect.fromLTWH(200.0, 0.0, 200.0, 600.0),
new Rect.fromLTWH(400.0, 0.0, 200.0, 600.0),
new Rect.fromLTWH(600.0, 0.0, 200.0, 600.0),
],
);
});
testWidgets('ListBody left', (WidgetTester tester) async {
await tester.pumpWidget(new Flex(
textDirection: TextDirection.ltr,
direction: Axis.horizontal,
children: <Widget>[
new Directionality(
textDirection: TextDirection.rtl,
child: new ListBody(mainAxis: Axis.horizontal, children: children),
),
],
));
expectRects(
tester,
<Rect>[
new Rect.fromLTWH(600.0, 0.0, 200.0, 600.0),
new Rect.fromLTWH(400.0, 0.0, 200.0, 600.0),
new Rect.fromLTWH(200.0, 0.0, 200.0, 600.0),
new Rect.fromLTWH(0.0, 0.0, 200.0, 600.0),
],
);
});
}
......@@ -285,6 +285,10 @@ abstract class Finder {
/// matched by this finder.
Finder get last => new _LastFinder(this);
/// Returns a variant of this finder that only matches the element at the
/// given index matched by this finder.
Finder at(int index) => new _IndexFinder(this, index);
/// Returns a variant of this finder that only matches elements reachable by
/// a hit test.
///
......@@ -335,6 +339,22 @@ class _LastFinder extends Finder {
}
}
class _IndexFinder extends Finder {
_IndexFinder(this.parent, this.index);
final Finder parent;
final int index;
@override
String get description => '${parent.description} (ignoring all but index $index)';
@override
Iterable<Element> apply(Iterable<Element> candidates) sync* {
yield parent.apply(candidates).elementAt(index);
}
}
class _HitTestableFinder extends Finder {
_HitTestableFinder(this.parent, this.alignment);
......
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