Unverified Commit 770a9b25 authored by Kate Lovett's avatar Kate Lovett Committed by GitHub

Updated Interactive Scrollbars (#71664)

parent e92df4ff
...@@ -680,4 +680,63 @@ void main() { ...@@ -680,4 +680,63 @@ void main() {
await tester.pump(_kScrollbarTimeToFade); await tester.pump(_kScrollbarTimeToFade);
await tester.pump(_kScrollbarFadeDuration); await tester.pump(_kScrollbarFadeDuration);
}); });
testWidgets('Tapping the track area pages the Scroll View', (WidgetTester tester) async {
final ScrollController scrollController = ScrollController();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: CupertinoScrollbar(
isAlwaysShown: true,
controller: scrollController,
child: SingleChildScrollView(
controller: scrollController,
child: const SizedBox(width: 1000.0, height: 1000.0),
),
),
),
),
);
await tester.pumpAndSettle();
expect(scrollController.offset, 0.0);
expect(
find.byType(CupertinoScrollbar),
paints..rrect(
color: _kScrollbarColor.color,
rrect: RRect.fromLTRBR(794.0, 3.0, 797.0, 359.4, const Radius.circular(1.5)),
)
);
// Tap on the track area below the thumb.
await tester.tapAt(const Offset(796.0, 550.0));
await tester.pumpAndSettle();
expect(scrollController.offset, 400.0);
expect(
find.byType(CupertinoScrollbar),
paints..rrect(
color: _kScrollbarColor.color,
rrect: RRect.fromRectAndRadius(
const Rect.fromLTRB(794.0, 240.6, 797.0, 597.0),
const Radius.circular(1.5),
),
)
);
// Tap on the track area above the thumb.
await tester.tapAt(const Offset(796.0, 50.0));
await tester.pumpAndSettle();
expect(scrollController.offset, 0.0);
expect(
find.byType(CupertinoScrollbar),
paints..rrect(
color: _kScrollbarColor.color,
rrect: RRect.fromLTRBR(794.0, 3.0, 797.0, 359.4, const Radius.circular(1.5)),
)
);
});
} }
...@@ -30,7 +30,7 @@ void main() { ...@@ -30,7 +30,7 @@ void main() {
)); ));
expect(find.byType(Scrollbar), isNot(paints..rect())); expect(find.byType(Scrollbar), isNot(paints..rect()));
await tester.fling(find.byType(SingleChildScrollView), const Offset(0.0, -10.0), 10.0); await tester.fling(find.byType(SingleChildScrollView), const Offset(0.0, -10.0), 10.0);
expect(find.byType(Scrollbar), paints..rect(rect: const Rect.fromLTRB(800.0 - 6.0, 1.5, 800.0, 91.5))); expect(find.byType(Scrollbar), paints..rect(rect: const Rect.fromLTRB(800.0 - 12.0, 0.0, 800.0, 600.0)));
}); });
testWidgets('Viewport basic test (RTL)', (WidgetTester tester) async { testWidgets('Viewport basic test (RTL)', (WidgetTester tester) async {
...@@ -40,7 +40,7 @@ void main() { ...@@ -40,7 +40,7 @@ void main() {
)); ));
expect(find.byType(Scrollbar), isNot(paints..rect())); expect(find.byType(Scrollbar), isNot(paints..rect()));
await tester.fling(find.byType(SingleChildScrollView), const Offset(0.0, -10.0), 10.0); await tester.fling(find.byType(SingleChildScrollView), const Offset(0.0, -10.0), 10.0);
expect(find.byType(Scrollbar), paints..rect(rect: const Rect.fromLTRB(0.0, 1.5, 6.0, 91.5))); expect(find.byType(Scrollbar), paints..rect(rect: const Rect.fromLTRB(0.0, 0.0, 12.0, 600.0)));
}); });
testWidgets('works with MaterialApp and Scaffold', (WidgetTester tester) async { testWidgets('works with MaterialApp and Scaffold', (WidgetTester tester) async {
...@@ -69,11 +69,11 @@ void main() { ...@@ -69,11 +69,11 @@ void main() {
expect(find.byType(Scrollbar), paints..rect( expect(find.byType(Scrollbar), paints..rect(
rect: const Rect.fromLTWH( rect: const Rect.fromLTWH(
800.0 - 6, // screen width - thickness 800.0 - 12, // screen width - default thickness and margin
0, // the paint area starts from the bottom of the app bar 0, // the paint area starts from the bottom of the app bar
6, // thickness 12, // thickness
// 56 being the height of the app bar // 56 being the height of the app bar
(600.0 - 56 - 34 - 20) / 4000 * (600 - 56 - 34 - 20), 600.0 - 56 - 34 - 20,
), ),
)); ));
}); });
......
...@@ -2,15 +2,20 @@ ...@@ -2,15 +2,20 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:ui' as ui;
import 'package:flutter/src/physics/utils.dart' show nearEqual; import 'package:flutter/src/physics/utils.dart' show nearEqual;
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../flutter_test_alternative.dart' show Fake; import '../flutter_test_alternative.dart' show Fake;
import '../rendering/mock_canvas.dart';
const Color _kScrollbarColor = Color(0xFF123456); const Color _kScrollbarColor = Color(0xFF123456);
const double _kThickness = 2.5; const double _kThickness = 2.5;
const double _kMinThumbExtent = 18.0; const double _kMinThumbExtent = 18.0;
const Duration _kScrollbarFadeDuration = Duration(milliseconds: 300);
const Duration _kScrollbarTimeToFade = Duration(milliseconds: 600);
ScrollbarPainter _buildPainter({ ScrollbarPainter _buildPainter({
TextDirection textDirection = TextDirection.ltr, TextDirection textDirection = TextDirection.ltr,
...@@ -45,6 +50,9 @@ class _DrawRectOnceCanvas extends Fake implements Canvas { ...@@ -45,6 +50,9 @@ class _DrawRectOnceCanvas extends Fake implements Canvas {
void drawRect(Rect rect, Paint paint) { void drawRect(Rect rect, Paint paint) {
rects.add(rect); rects.add(rect);
} }
@override
void drawLine(Offset p1, Offset p2, Paint paint) {}
} }
void main() { void main() {
...@@ -503,4 +511,245 @@ void main() { ...@@ -503,4 +511,245 @@ void main() {
} }
}, },
); );
testWidgets('ScrollbarPainter asserts if no TextDirection has been provided', (WidgetTester tester) async {
final ScrollbarPainter painter = ScrollbarPainter(
color: _kScrollbarColor,
fadeoutOpacityAnimation: kAlwaysCompleteAnimation,
);
const Size size = Size(60, 80);
final ScrollMetrics scrollMetrics = defaultMetrics.copyWith(
maxScrollExtent: 100000,
viewportDimension: size.height,
);
painter.update(scrollMetrics, scrollMetrics.axisDirection);
// Try to paint the scrollbar
try {
painter.paint(testCanvas, size);
} on AssertionError catch (error) {
expect(error.message, 'A TextDirection must be provided before a Scrollbar can be painted.');
}
});
testWidgets('Tapping the track area pages the Scroll View', (WidgetTester tester) async {
final ScrollController scrollController = ScrollController();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: RawScrollbar(
isAlwaysShown: true,
controller: scrollController,
child: SingleChildScrollView(
controller: scrollController,
child: const SizedBox(width: 1000.0, height: 1000.0),
),
),
),
),
);
await tester.pumpAndSettle();
expect(scrollController.offset, 0.0);
expect(
find.byType(RawScrollbar),
paints
..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
..rect(
rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 360.0),
color: const Color(0x66BCBCBC),
)
);
// Tap on the track area below the thumb.
await tester.tapAt(const Offset(796.0, 550.0));
await tester.pumpAndSettle();
expect(scrollController.offset, 400.0);
expect(
find.byType(RawScrollbar),
paints
..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
..rect(
rect: const Rect.fromLTRB(794.0, 240.0, 800.0, 600.0),
color: const Color(0x66BCBCBC),
)
);
// Tap on the track area above the thumb.
await tester.tapAt(const Offset(796.0, 50.0));
await tester.pumpAndSettle();
expect(scrollController.offset, 0.0);
expect(
find.byType(RawScrollbar),
paints
..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
..rect(
rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 360.0),
color: const Color(0x66BCBCBC),
)
);
});
testWidgets('Scrollbar never goes away until finger lift', (WidgetTester tester) async {
await tester.pumpWidget(
const Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: MediaQueryData(),
child: RawScrollbar(
child: SingleChildScrollView(
child: SizedBox(width: 4000.0, height: 4000.0)
),
),
),
),
);
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(SingleChildScrollView)));
await gesture.moveBy(const Offset(0.0, -20.0));
await tester.pump();
// Scrollbar fully showing
await tester.pump(const Duration(milliseconds: 500));
expect(
find.byType(RawScrollbar),
paints
..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
..rect(
rect: const Rect.fromLTRB(794.0, 3.0, 800.0, 93.0),
color: const Color(0x66BCBCBC),
),
);
await tester.pump(const Duration(seconds: 3));
await tester.pump(const Duration(seconds: 3));
// Still there.
expect(
find.byType(RawScrollbar),
paints
..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
..rect(
rect: const Rect.fromLTRB(794.0, 3.0, 800.0, 93.0),
color: const Color(0x66BCBCBC),
),
);
await gesture.up();
await tester.pump(_kScrollbarTimeToFade);
await tester.pump(_kScrollbarFadeDuration * 0.5);
// Opacity going down now.
expect(
find.byType(RawScrollbar),
paints
..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
..rect(
rect: const Rect.fromLTRB(794.0, 3.0, 800.0, 93.0),
color: const Color(0x4fbcbcbc),
),
);
});
testWidgets('Scrollbar does not fade away while hovering', (WidgetTester tester) async {
await tester.pumpWidget(
const Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: MediaQueryData(),
child: RawScrollbar(
child: SingleChildScrollView(
child: SizedBox(width: 4000.0, height: 4000.0)
),
),
),
),
);
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(SingleChildScrollView)));
await gesture.moveBy(const Offset(0.0, -20.0));
await tester.pump();
// Scrollbar fully showing
await tester.pump(const Duration(milliseconds: 500));
expect(
find.byType(RawScrollbar),
paints
..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
..rect(
rect: const Rect.fromLTRB(794.0, 3.0, 800.0, 93.0),
color: const Color(0x66BCBCBC),
),
);
final TestPointer testPointer = TestPointer(1, ui.PointerDeviceKind.mouse);
// Hover over the thumb to prevent the scrollbar from fading out.
testPointer.hover(const Offset(790.0, 5.0));
await gesture.up();
await tester.pump(const Duration(seconds: 3));
// Still there.
expect(
find.byType(RawScrollbar),
paints
..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
..rect(
rect: const Rect.fromLTRB(794.0, 3.0, 800.0, 93.0),
color: const Color(0x66BCBCBC),
),
);
});
testWidgets('Scrollbar thumb can be dragged', (WidgetTester tester) async {
final ScrollController scrollController = ScrollController();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: PrimaryScrollController(
controller: scrollController,
child: RawScrollbar(
isAlwaysShown: true,
controller: scrollController,
child: const SingleChildScrollView(
child: SizedBox(width: 4000.0, height: 4000.0)
),
),
),
),
),
);
await tester.pumpAndSettle();
expect(scrollController.offset, 0.0);
expect(
find.byType(RawScrollbar),
paints
..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
..rect(
rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 90.0),
color: const Color(0x66BCBCBC),
),
);
// Drag the thumb down to scroll down.
const double scrollAmount = 10.0;
final TestGesture dragScrollbarGesture = await tester.startGesture(const Offset(797.0, 45.0));
await tester.pumpAndSettle();
await dragScrollbarGesture.moveBy(const Offset(0.0, scrollAmount));
await tester.pumpAndSettle();
await dragScrollbarGesture.up();
await tester.pumpAndSettle();
// The view has scrolled more than it would have by a swipe gesture of the
// same distance.
expect(scrollController.offset, greaterThan(scrollAmount * 2));
expect(
find.byType(RawScrollbar),
paints
..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
..rect(
rect: const Rect.fromLTRB(794.0, 10.0, 800.0, 100.0),
color: const Color(0x66BCBCBC),
),
);
});
} }
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