|
| 1 | +// Copyright 2014 The Flutter Authors. All rights reserved. |
| 2 | +// Use of this source code is governed by a BSD-style license that can be |
| 3 | +// found in the LICENSE file. |
| 4 | + |
| 5 | +import 'dart:async'; |
| 6 | +import 'dart:ui'; |
| 7 | + |
| 8 | +import 'package:flutter/gestures.dart'; |
| 9 | +import 'package:flutter/rendering.dart'; |
| 10 | +import 'package:flutter/widgets.dart'; |
| 11 | +import 'package:flutter/scheduler.dart'; |
| 12 | +import 'package:flutter_test/flutter_test.dart'; |
| 13 | + |
| 14 | +import 'recorder.dart'; |
| 15 | + |
| 16 | +/// Creates a grid of mouse regions, then continuously scrolls them up and down. |
| 17 | +/// |
| 18 | +/// Measures our ability to render mouse regions. |
| 19 | +class BenchMouseRegionGridScroll extends WidgetRecorder { |
| 20 | + BenchMouseRegionGridScroll() : super(name: benchmarkName); |
| 21 | + |
| 22 | + static const String benchmarkName = 'bench_mouse_region_grid_scroll'; |
| 23 | + |
| 24 | + final _Tester tester = _Tester(); |
| 25 | + |
| 26 | + // Use a non-trivial border to force Web to switch painter |
| 27 | + Border _getBorder(int columnIndex, int rowIndex) { |
| 28 | + const BorderSide defaultBorderSide = BorderSide(); |
| 29 | + |
| 30 | + return Border( |
| 31 | + left: columnIndex == 0 ? defaultBorderSide : BorderSide.none, |
| 32 | + top: rowIndex == 0 ? defaultBorderSide : BorderSide.none, |
| 33 | + right: defaultBorderSide, |
| 34 | + bottom: defaultBorderSide, |
| 35 | + ); |
| 36 | + } |
| 37 | + |
| 38 | + bool started = false; |
| 39 | + |
| 40 | + @override |
| 41 | + void frameDidDraw() { |
| 42 | + if (!started) { |
| 43 | + started = true; |
| 44 | + SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) async { |
| 45 | + tester.start(); |
| 46 | + final VoidCallback localDidStop = didStop; |
| 47 | + didStop = () { |
| 48 | + if (localDidStop != null) |
| 49 | + localDidStop(); |
| 50 | + tester.stop(); |
| 51 | + }; |
| 52 | + }); |
| 53 | + } |
| 54 | + super.frameDidDraw(); |
| 55 | + } |
| 56 | + |
| 57 | + @override |
| 58 | + Widget createWidget() { |
| 59 | + const int rowsCount = 60; |
| 60 | + const int columnsCount = 20; |
| 61 | + const double containerSize = 20; |
| 62 | + return Directionality( |
| 63 | + textDirection: TextDirection.ltr, |
| 64 | + child: Align( |
| 65 | + alignment: Alignment.topLeft, |
| 66 | + child: SizedBox( |
| 67 | + width: 400, |
| 68 | + height: 400, |
| 69 | + child: ListView.builder( |
| 70 | + itemCount: rowsCount, |
| 71 | + cacheExtent: rowsCount * containerSize, |
| 72 | + physics: const ClampingScrollPhysics(), |
| 73 | + itemBuilder: (BuildContext context, int rowIndex) => Row( |
| 74 | + children: List<Widget>.generate( |
| 75 | + columnsCount, |
| 76 | + (int columnIndex) => MouseRegion( |
| 77 | + onEnter: (_) {}, |
| 78 | + child: Container( |
| 79 | + decoration: BoxDecoration( |
| 80 | + border: _getBorder(columnIndex, rowIndex), |
| 81 | + color: Color.fromARGB(255, rowIndex * 20 % 256, 127, 127), |
| 82 | + ), |
| 83 | + width: containerSize, |
| 84 | + height: containerSize, |
| 85 | + ), |
| 86 | + ), |
| 87 | + ), |
| 88 | + ), |
| 89 | + ), |
| 90 | + ), |
| 91 | + ), |
| 92 | + ); |
| 93 | + } |
| 94 | +} |
| 95 | + |
| 96 | +class _UntilNextFrame { |
| 97 | + _UntilNextFrame._(); |
| 98 | + |
| 99 | + static Completer<void> _completer; |
| 100 | + |
| 101 | + static Future<void> wait() { |
| 102 | + if (_UntilNextFrame._completer == null) { |
| 103 | + _UntilNextFrame._completer = Completer<void>(); |
| 104 | + SchedulerBinding.instance.addPostFrameCallback((_) { |
| 105 | + _UntilNextFrame._completer.complete(null); |
| 106 | + _UntilNextFrame._completer = null; |
| 107 | + }); |
| 108 | + } |
| 109 | + return _UntilNextFrame._completer.future; |
| 110 | + } |
| 111 | +} |
| 112 | + |
| 113 | +class _Tester { |
| 114 | + static const int scrollFrequency = 60; |
| 115 | + static const Offset dragStartLocation = Offset(200, 200); |
| 116 | + static const Offset dragUpOffset = Offset(0, 200); |
| 117 | + static const Offset dragDownOffset = Offset(0, -200); |
| 118 | + static const Duration dragDuration = Duration(milliseconds: 200); |
| 119 | + |
| 120 | + bool _stopped = false; |
| 121 | + |
| 122 | + TestGesture get gesture { |
| 123 | + return _gesture ??= TestGesture( |
| 124 | + dispatcher: (PointerEvent event, HitTestResult result) async { |
| 125 | + RendererBinding.instance.dispatchEvent(event, result); |
| 126 | + }, |
| 127 | + hitTester: (Offset location) { |
| 128 | + final HitTestResult result = HitTestResult(); |
| 129 | + RendererBinding.instance.hitTest(result, location); |
| 130 | + return result; |
| 131 | + }, |
| 132 | + kind: PointerDeviceKind.mouse, |
| 133 | + ); |
| 134 | + } |
| 135 | + TestGesture _gesture; |
| 136 | + |
| 137 | + Duration currentTime = Duration.zero; |
| 138 | + |
| 139 | + Future<void> _scroll(Offset start, Offset offset, Duration duration) async { |
| 140 | + final int durationMs = duration.inMilliseconds; |
| 141 | + final Duration fullFrameDuration = const Duration(seconds: 1) ~/ scrollFrequency; |
| 142 | + final int frameDurationMs = fullFrameDuration.inMilliseconds; |
| 143 | + |
| 144 | + final int fullFrames = duration.inMilliseconds ~/ frameDurationMs; |
| 145 | + final Offset fullFrameOffset = offset * ((frameDurationMs as double) / durationMs); |
| 146 | + |
| 147 | + final Duration finalFrameDuration = duration - fullFrameDuration * fullFrames; |
| 148 | + final Offset finalFrameOffset = offset - fullFrameOffset * (fullFrames as double); |
| 149 | + |
| 150 | + await gesture.down(start, timeStamp: currentTime); |
| 151 | + |
| 152 | + for (int frame = 0; frame < fullFrames; frame += 1) { |
| 153 | + currentTime += fullFrameDuration; |
| 154 | + await gesture.moveBy(fullFrameOffset, timeStamp: currentTime); |
| 155 | + await _UntilNextFrame.wait(); |
| 156 | + } |
| 157 | + |
| 158 | + if (finalFrameOffset != Offset.zero) { |
| 159 | + currentTime += finalFrameDuration; |
| 160 | + await gesture.moveBy(finalFrameOffset, timeStamp: currentTime); |
| 161 | + await _UntilNextFrame.wait(); |
| 162 | + } |
| 163 | + |
| 164 | + await gesture.up(timeStamp: currentTime); |
| 165 | + } |
| 166 | + |
| 167 | + Future<void> start() async { |
| 168 | + await Future<void>.delayed(Duration.zero); |
| 169 | + while (!_stopped) { |
| 170 | + await _scroll(dragStartLocation, dragUpOffset, dragDuration); |
| 171 | + await _scroll(dragStartLocation, dragDownOffset, dragDuration); |
| 172 | + } |
| 173 | + } |
| 174 | + |
| 175 | + void stop() { |
| 176 | + _stopped = true; |
| 177 | + } |
| 178 | +} |
0 commit comments