| // Copyright 2014 The Flutter 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'; |
| |
| import 'package:flutter/gestures.dart'; |
| import 'package:flutter/rendering.dart'; |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter/scheduler.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| import 'recorder.dart'; |
| |
| /// Creates a grid of mouse regions, then continuously scrolls them up and down. |
| /// |
| /// Measures our ability to render mouse regions. |
| class BenchMouseRegionGridScroll extends WidgetRecorder { |
| BenchMouseRegionGridScroll() : super(name: benchmarkName); |
| |
| static const String benchmarkName = 'bench_mouse_region_grid_scroll'; |
| |
| final _Tester tester = _Tester(); |
| |
| // Use a non-trivial border to force Web to switch painter |
| Border _getBorder(int columnIndex, int rowIndex) { |
| const BorderSide defaultBorderSide = BorderSide(); |
| |
| return Border( |
| left: columnIndex == 0 ? defaultBorderSide : BorderSide.none, |
| top: rowIndex == 0 ? defaultBorderSide : BorderSide.none, |
| right: defaultBorderSide, |
| bottom: defaultBorderSide, |
| ); |
| } |
| |
| bool started = false; |
| |
| @override |
| void frameDidDraw() { |
| if (!started) { |
| started = true; |
| SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) async { |
| tester.start(); |
| registerDidStop(tester.stop); |
| }); |
| } |
| super.frameDidDraw(); |
| } |
| |
| @override |
| Widget createWidget() { |
| const int rowsCount = 60; |
| const int columnsCount = 20; |
| const double containerSize = 20; |
| return Directionality( |
| textDirection: TextDirection.ltr, |
| child: Align( |
| alignment: Alignment.topLeft, |
| child: SizedBox( |
| width: 400, |
| height: 400, |
| child: ListView.builder( |
| itemCount: rowsCount, |
| cacheExtent: rowsCount * containerSize, |
| physics: const ClampingScrollPhysics(), |
| itemBuilder: (BuildContext context, int rowIndex) => Row( |
| children: List<Widget>.generate( |
| columnsCount, |
| (int columnIndex) => MouseRegion( |
| onEnter: (_) {}, |
| child: Container( |
| decoration: BoxDecoration( |
| border: _getBorder(columnIndex, rowIndex), |
| color: Color.fromARGB(255, rowIndex * 20 % 256, 127, 127), |
| ), |
| width: containerSize, |
| height: containerSize, |
| ), |
| ), |
| ), |
| ), |
| ), |
| ), |
| ), |
| ); |
| } |
| } |
| |
| class _UntilNextFrame { |
| _UntilNextFrame._(); |
| |
| static Completer<void> _completer; |
| |
| static Future<void> wait() { |
| if (_UntilNextFrame._completer == null) { |
| _UntilNextFrame._completer = Completer<void>(); |
| SchedulerBinding.instance.addPostFrameCallback((_) { |
| _UntilNextFrame._completer.complete(null); |
| _UntilNextFrame._completer = null; |
| }); |
| } |
| return _UntilNextFrame._completer.future; |
| } |
| } |
| |
| class _Tester { |
| static const int scrollFrequency = 60; |
| static const Offset dragStartLocation = Offset(200, 200); |
| static const Offset dragUpOffset = Offset(0, 200); |
| static const Offset dragDownOffset = Offset(0, -200); |
| static const Duration dragDuration = Duration(milliseconds: 200); |
| |
| bool _stopped = false; |
| |
| TestGesture get gesture { |
| return _gesture ??= TestGesture( |
| dispatcher: (PointerEvent event) async { |
| RendererBinding.instance.handlePointerEvent(event); |
| }, |
| kind: PointerDeviceKind.mouse, |
| ); |
| } |
| TestGesture _gesture; |
| |
| Duration currentTime = Duration.zero; |
| |
| Future<void> _scroll(Offset start, Offset offset, Duration duration) async { |
| final int durationMs = duration.inMilliseconds; |
| final Duration fullFrameDuration = const Duration(seconds: 1) ~/ scrollFrequency; |
| final int frameDurationMs = fullFrameDuration.inMilliseconds; |
| |
| final int fullFrames = duration.inMilliseconds ~/ frameDurationMs; |
| final Offset fullFrameOffset = offset * ((frameDurationMs as double) / durationMs); |
| |
| final Duration finalFrameDuration = duration - fullFrameDuration * fullFrames; |
| final Offset finalFrameOffset = offset - fullFrameOffset * (fullFrames as double); |
| |
| await gesture.down(start, timeStamp: currentTime); |
| |
| for (int frame = 0; frame < fullFrames; frame += 1) { |
| currentTime += fullFrameDuration; |
| await gesture.moveBy(fullFrameOffset, timeStamp: currentTime); |
| await _UntilNextFrame.wait(); |
| } |
| |
| if (finalFrameOffset != Offset.zero) { |
| currentTime += finalFrameDuration; |
| await gesture.moveBy(finalFrameOffset, timeStamp: currentTime); |
| await _UntilNextFrame.wait(); |
| } |
| |
| await gesture.up(timeStamp: currentTime); |
| } |
| |
| Future<void> start() async { |
| await Future<void>.delayed(Duration.zero); |
| while (!_stopped) { |
| await _scroll(dragStartLocation, dragUpOffset, dragDuration); |
| await _scroll(dragStartLocation, dragDownOffset, dragDuration); |
| } |
| } |
| |
| void stop() { |
| _stopped = true; |
| } |
| } |