Commit 4150615e authored by Ian Hickson's avatar Ian Hickson

Merge pull request #1401 from Hixie/lists

Make hit testing work in horizontal scrolling list
parents 156ff2be f103d397
......@@ -589,7 +589,7 @@ class PageableList<T> extends ScrollableList<T> {
ItemBuilder<T> itemBuilder,
bool itemsWrap: false,
double itemExtent,
PageChangedCallback this.pageChanged,
this.onPageChanged,
EdgeDims padding,
this.duration: const Duration(milliseconds: 200),
this.curve: ease
......@@ -607,7 +607,7 @@ class PageableList<T> extends ScrollableList<T> {
final Duration duration;
final Curve curve;
final PageChangedCallback pageChanged;
final PageChangedCallback onPageChanged;
PageableListState<T> createState() => new PageableListState();
}
......@@ -633,8 +633,8 @@ class PageableListState<T> extends ScrollableListState<T, PageableList<T>> {
int get currentPage => (scrollOffset / config.itemExtent).floor() % itemCount;
void _notifyPageChanged(_) {
if (config.pageChanged != null)
config.pageChanged(currentPage);
if (config.onPageChanged != null)
config.onPageChanged(currentPage);
}
void settleScrollOffset() {
......
......@@ -377,11 +377,17 @@ class RenderBlockViewport extends RenderBlockBase {
void applyPaintTransform(Matrix4 transform) {
super.applyPaintTransform(transform);
transform.translate(0.0, startOffset);
if (isVertical)
transform.translate(0.0, startOffset);
else
transform.translate(startOffset, 0.0);
}
void hitTestChildren(HitTestResult result, { Point position }) {
defaultHitTestChildren(result, position: position + new Offset(0.0, -startOffset));
if (isVertical)
defaultHitTestChildren(result, position: position + new Offset(0.0, -startOffset));
else
defaultHitTestChildren(result, position: position + new Offset(-startOffset, 0.0));
}
String debugDescribeSettings(String prefix) => '${super.debugDescribeSettings(prefix)}${prefix}startOffset: ${startOffset}\n';
......
......@@ -25,15 +25,21 @@ class RootComponentState extends State<RootComponent> {
class WidgetTester {
// See thttps://github.com/flutter/engine/issues/1084 regarding frameTimeMs vs FakeAsync
void pumpFrame(Widget widget, [ double frameTimeMs = 0.0 ]) {
runApp(widget);
scheduler.beginFrame(frameTimeMs); // TODO(ianh): https://github.com/flutter/engine/issues/1084
scheduler.beginFrame(frameTimeMs);
}
void pumpFrameWithoutChange([ double frameTimeMs = 0.0 ]) {
scheduler.beginFrame(frameTimeMs); // TODO(ianh): https://github.com/flutter/engine/issues/1084
scheduler.beginFrame(frameTimeMs);
}
void reset() {
runApp(new Container());
scheduler.beginFrame(0.0);
}
List<Layer> _layers(Layer layer) {
List<Layer> result = [layer];
......
import 'package:quiver/testing/async.dart';
import 'package:sky/widgets.dart';
import 'package:sky/src/fn3.dart';
import 'package:test/test.dart';
import 'widget_tester.dart';
import '../fn3/widget_tester.dart';
const Size pageSize = const Size(800.0, 600.0);
const List<int> pages = const <int>[0, 1, 2, 3, 4, 5];
int currentPage = null;
bool itemsWrap = false;
Widget buildPage(int page) {
Widget buildPage(BuildContext context, int page) {
return new Container(
key: new ValueKey<int>(page),
width: pageSize.width,
......@@ -20,14 +20,14 @@ Widget buildPage(int page) {
Widget buildFrame() {
// The test framework forces the frame (and so the PageableList)
// to be 800x600. The pageSize constant reflects as much.
// to be 800x600. The pageSize constant reflects this.
return new PageableList<int>(
items: pages,
itemBuilder: buildPage,
itemsWrap: itemsWrap,
itemExtent: pageSize.width,
scrollDirection: ScrollDirection.horizontal,
pageChanged: (int page) { currentPage = page; }
onPageChanged: (int page) { currentPage = page; }
);
}
......@@ -36,8 +36,8 @@ void page(WidgetTester tester, Offset offset) {
new FakeAsync().run((async) {
tester.scroll(tester.findText(itemText), offset);
// One frame to start the animation, a second to complete it.
tester.pumpFrame(buildFrame);
tester.pumpFrame(buildFrame, 1000.0);
tester.pumpFrameWithoutChange();
tester.pumpFrameWithoutChange(1000.0);
async.elapse(new Duration(seconds: 1));
});
}
......@@ -57,42 +57,55 @@ void main() {
WidgetTester tester = new WidgetTester();
currentPage = null;
itemsWrap = false;
tester.pumpFrame(buildFrame);
tester.pumpFrame(buildFrame());
expect(currentPage, isNull);
pageLeft(tester);
expect(currentPage, equals(1));
});
test('Underscroll (scroll right), return to page 0', () {
test('Scroll right from page 1 to page 0', () {
WidgetTester tester = new WidgetTester();
currentPage = null;
itemsWrap = false;
tester.pumpFrame(buildFrame);
expect(currentPage, isNull);
tester.pumpFrame(buildFrame());
expect(currentPage, equals(1));
pageRight(tester);
expect(currentPage, equals(0));
});
// PageableList with itemsWrap: true
test('Scroll right from page 0 does nothing (underscroll)', () {
WidgetTester tester = new WidgetTester();
itemsWrap = false;
tester.pumpFrame(buildFrame());
expect(currentPage, equals(0));
pageRight(tester);
expect(currentPage, equals(0));
});
itemsWrap = true;
// PageableList with itemsWrap: true
test('Scroll left page 0 to page 1, itemsWrap: true', () {
WidgetTester tester = new WidgetTester();
tester.reset();
currentPage = null;
itemsWrap = true;
tester.pumpFrame(buildFrame);
tester.pumpFrame(buildFrame());
expect(currentPage, isNull);
pageLeft(tester);
expect(currentPage, equals(1));
});
test('Scroll right from page 0 to page 5, itemsWrap: true', () {
test('Scroll right from page 1 to page 0, itemsWrap: true', () {
WidgetTester tester = new WidgetTester();
currentPage = null;
itemsWrap = true;
tester.pumpFrame(buildFrame);
expect(currentPage, isNull);
tester.pumpFrame(buildFrame());
expect(currentPage, equals(1));
pageRight(tester);
expect(currentPage, equals(0));
});
test('Scroll right from page 0 to page 5, itemsWrap: true (underscroll)', () {
WidgetTester tester = new WidgetTester();
tester.pumpFrame(buildFrame());
expect(currentPage, equals(0));
pageRight(tester);
expect(currentPage, equals(5));
});
......
import 'package:quiver/testing/async.dart';
import 'package:sky/rendering.dart';
import 'package:sky/src/fn3.dart';
import 'package:test/test.dart';
import '../fn3/widget_tester.dart';
const List<int> items = const <int>[0, 1, 2, 3, 4, 5];
List<int> tapped = <int>[];
Widget buildFrame() {
return ;
}
void main() {
double t = 0.0;
WidgetTester tester = new WidgetTester();
test('Tap item after scroll - horizontal', () {
tester.pumpFrame(new Center(
child: new Container(
height: 50.0,
child: new ScrollableList<int>(
key: new GlobalKey(),
items: items,
itemBuilder: (BuildContext context, int item) {
return new Container(
key: new ValueKey<int>(item),
child: new GestureDetector(
onTap: () { tapped.add(item); },
child: new Text('$item')
)
);
},
itemExtent: 290.0,
scrollDirection: ScrollDirection.horizontal
)
)
), t);
tester.scroll(tester.findText('2'), const Offset(-280.0, 0.0));
tester.pumpFrameWithoutChange(t += 1000.0);
// screen is 800px wide, and has the following items:
// -280..10 = 0
// 10..300 = 1
// 300..590 = 2
// 590..880 = 3
expect(tester.findText('0'), isNotNull);
expect(tester.findText('1'), isNotNull);
expect(tester.findText('2'), isNotNull);
expect(tester.findText('3'), isNotNull);
expect(tester.findText('4'), isNull);
expect(tester.findText('5'), isNull);
expect(tapped, equals([]));
tester.tap(tester.findText('2'));
expect(tapped, equals([2]));
});
test('Tap item after scroll - vertical', () {
tester.pumpFrame(new Center(
child: new Container(
width: 50.0,
child: new ScrollableList<int>(
key: new GlobalKey(),
items: items,
itemBuilder: (BuildContext context, int item) {
return new Container(
key: new ValueKey<int>(item),
child: new GestureDetector(
onTap: () { tapped.add(item); },
child: new Text('$item')
)
);
},
itemExtent: 290.0,
scrollDirection: ScrollDirection.vertical
)
)
), t);
tester.scroll(tester.findText('1'), const Offset(0.0, -280.0));
tester.pumpFrameWithoutChange(t += 1000.0);
// screen is 600px tall, and has the following items:
// -280..10 = 0
// 10..300 = 1
// 300..590 = 2
// 590..880 = 3
expect(tester.findText('0'), isNotNull);
expect(tester.findText('1'), isNotNull);
expect(tester.findText('2'), isNotNull);
expect(tester.findText('3'), isNotNull);
expect(tester.findText('4'), isNull);
expect(tester.findText('5'), isNull);
expect(tapped, equals([2]));
tester.tap(tester.findText('1'));
expect(tapped, equals([2, 1]));
tester.tap(tester.findText('3'));
expect(tapped, equals([2, 1])); // the center of the third item is off-screen so it shouldn't get hit
});
}
import 'package:quiver/testing/async.dart';
import 'package:sky/rendering.dart';
import 'package:sky/src/fn3.dart';
import 'package:test/test.dart';
import '../fn3/widget_tester.dart';
const List<int> items = const <int>[0, 1, 2, 3, 4, 5];
Widget buildFrame() {
return new Center(
child: new Container(
height: 50.0,
child: new ScrollableList<int>(
items: items,
itemBuilder: (BuildContext context, int item) {
return new Container(
key: new ValueKey<int>(item),
child: new Text('$item')
);
},
itemExtent: 290.0,
scrollDirection: ScrollDirection.horizontal
)
)
);
}
void main() {
double t = 0.0;
WidgetTester tester = new WidgetTester();
tester.pumpFrame(buildFrame());
test('Drag to the left using item 1', () {
tester.pumpFrameWithoutChange(t += 1000.0);
tester.scroll(tester.findText('1'), const Offset(-300.0, 0.0));
tester.pumpFrameWithoutChange(t += 1000.0);
// screen is 800px wide, and has the following items:
// -10..280 = 1
// 280..570 = 2
// 570..860 = 3
expect(tester.findText('0'), isNull);
expect(tester.findText('1'), isNotNull);
expect(tester.findText('2'), isNotNull);
expect(tester.findText('3'), isNotNull);
expect(tester.findText('4'), isNull);
expect(tester.findText('5'), isNull);
});
test('Drag to the left using item 3', () {
// the center of item 3 is visible, so this works;
// if item 3 was a bit wider, such that it's center was past the 800px mark, this would fail,
// because it wouldn't be hit tested when scrolling from its center, as scroll() does.
tester.pumpFrameWithoutChange(t += 1000.0);
tester.scroll(tester.findText('3'), const Offset(-290.0, 0.0));
tester.pumpFrameWithoutChange(t += 1000.0);
// screen is 800px wide, and has the following items:
// -10..280 = 2
// 280..570 = 3
// 570..860 = 4
expect(tester.findText('0'), isNull);
expect(tester.findText('1'), isNull);
expect(tester.findText('2'), isNotNull);
expect(tester.findText('3'), isNotNull);
expect(tester.findText('4'), isNotNull);
expect(tester.findText('5'), isNull);
});
test('Drag up using item 3', () {
tester.pumpFrameWithoutChange(t += 1000.0);
tester.scroll(tester.findText('3'), const Offset(0.0, -290.0));
tester.pumpFrameWithoutChange(t += 1000.0);
// unchanged
expect(tester.findText('0'), isNull);
expect(tester.findText('1'), isNull);
expect(tester.findText('2'), isNotNull);
expect(tester.findText('3'), isNotNull);
expect(tester.findText('4'), isNotNull);
expect(tester.findText('5'), isNull);
});
test('Drag to the left using item 3 again', () {
tester.pumpFrameWithoutChange(t += 1000.0);
tester.scroll(tester.findText('3'), const Offset(-290.0, 0.0));
tester.pumpFrameWithoutChange(t += 1000.0);
// screen is 800px wide, and has the following items:
// -10..280 = 3
// 280..570 = 4
// 570..860 = 5
expect(tester.findText('0'), isNull);
expect(tester.findText('1'), isNull);
expect(tester.findText('2'), isNull);
expect(tester.findText('3'), isNotNull);
expect(tester.findText('4'), isNotNull);
expect(tester.findText('5'), isNotNull);
});
test('Drag to the left using item 3 again again (past the end of the list)', () {
tester.pumpFrameWithoutChange(t += 1000.0);
// at this point we can drag 60 pixels further before we hit the friction zone
// then, every pixel we drag is equivalent to half a pixel of movement
// to move item 3 entirely off screen therefore takes:
// 60 + (290-60)*2 = 520 pixels
// plus a couple more to be sure
tester.scroll(tester.findText('3'), const Offset(-522.0, 0.0));
tester.pumpFrameWithoutChange(t += 0.0); // just after release
// screen is 800px wide, and has the following items:
// -11..279 = 4
// 279..569 = 5
expect(tester.findText('0'), isNull);
expect(tester.findText('1'), isNull);
expect(tester.findText('2'), isNull);
expect(tester.findText('3'), isNull);
expect(tester.findText('4'), isNotNull);
expect(tester.findText('5'), isNotNull);
tester.pumpFrameWithoutChange(t += 1000.0); // a second after release
// screen is 800px wide, and has the following items:
// -70..220 = 3
// 220..510 = 4
// 510..800 = 5
expect(tester.findText('0'), isNull);
expect(tester.findText('1'), isNull);
expect(tester.findText('2'), isNull);
expect(tester.findText('3'), isNotNull);
expect(tester.findText('4'), isNotNull);
expect(tester.findText('5'), isNotNull);
});
test('Drag to the left using item 2 when the scroll offset is big', () {
tester.reset();
tester.pumpFrame(buildFrame(), t += 1000.0);
tester.scroll(tester.findText('2'), const Offset(-280.0, 0.0));
tester.pumpFrameWithoutChange(t += 1000.0);
// screen is 800px wide, and has the following items:
// -280..10 = 0
// 10..300 = 1
// 300..590 = 2
// 590..880 = 3
expect(tester.findText('0'), isNotNull);
expect(tester.findText('1'), isNotNull);
expect(tester.findText('2'), isNotNull);
expect(tester.findText('3'), isNotNull);
expect(tester.findText('4'), isNull);
expect(tester.findText('5'), isNull);
tester.pumpFrameWithoutChange(t += 1000.0);
tester.scroll(tester.findText('2'), const Offset(-290.0, 0.0));
tester.pumpFrameWithoutChange(t += 1000.0);
// screen is 800px wide, and has the following items:
// -280..10 = 1
// 10..300 = 2
// 300..590 = 3
// 590..880 = 4
expect(tester.findText('0'), isNull);
expect(tester.findText('1'), isNotNull);
expect(tester.findText('2'), isNotNull);
expect(tester.findText('3'), isNotNull);
expect(tester.findText('4'), isNotNull);
expect(tester.findText('5'), isNull);
});
}
import 'package:quiver/testing/async.dart';
import 'package:sky/src/fn3.dart';
import 'package:test/test.dart';
import '../fn3/widget_tester.dart';
const List<int> items = const <int>[0, 1, 2, 3, 4, 5];
Widget buildFrame() {
return new ScrollableList<int>(
items: items,
itemBuilder: (BuildContext context, int item) {
return new Container(
key: new ValueKey<int>(item),
child: new Text('$item')
);
},
itemExtent: 290.0,
scrollDirection: ScrollDirection.vertical
);
}
void main() {
WidgetTester tester = new WidgetTester();
tester.pumpFrame(buildFrame());
test('Drag up using item 1', () {
tester.pumpFrameWithoutChange();
tester.scroll(tester.findText('1'), const Offset(0.0, -300.0));
tester.pumpFrameWithoutChange();
// screen is 600px high, and has the following items:
// -10..280 = 1
// 280..570 = 2
// 570..860 = 3
expect(tester.findText('0'), isNull);
expect(tester.findText('1'), isNotNull);
expect(tester.findText('2'), isNotNull);
expect(tester.findText('3'), isNotNull);
expect(tester.findText('4'), isNull);
expect(tester.findText('5'), isNull);
});
test('Drag up using item 2', () {
tester.pumpFrameWithoutChange();
tester.scroll(tester.findText('2'), const Offset(0.0, -290.0));
tester.pumpFrameWithoutChange();
// screen is 600px high, and has the following items:
// -10..280 = 2
// 280..570 = 3
// 570..860 = 4
expect(tester.findText('0'), isNull);
expect(tester.findText('1'), isNull);
expect(tester.findText('2'), isNotNull);
expect(tester.findText('3'), isNotNull);
expect(tester.findText('4'), isNotNull);
expect(tester.findText('5'), isNull);
});
test('Drag to the left using item 3', () {
tester.pumpFrameWithoutChange();
tester.scroll(tester.findText('3'), const Offset(-300.0, 0.0));
tester.pumpFrameWithoutChange();
// nothing should have changed
expect(tester.findText('0'), isNull);
expect(tester.findText('1'), isNull);
expect(tester.findText('2'), isNotNull);
expect(tester.findText('3'), isNotNull);
expect(tester.findText('4'), isNotNull);
expect(tester.findText('5'), isNull);
});
}
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