blob: 4c48f1cb5b50dc404288b888f9d0deb92bf297e8 [file] [log] [blame]
// Copyright 2020 The Fuchsia 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:developer';
import 'dart:ui' as ui;
import 'package:args/args.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:lib.widgets/utils.dart';
void main([List<String> args = const []]) {
final argParser = ArgParser()
..addOption('sampling-offset-ms',
abbr: 'o',
valueHelp: 'n',
help: 'Sample touch events with offset.',
defaultsTo: '-38')
..addFlag('resample', defaultsTo: true)
..addFlag('help',
abbr: 'h', help: 'Displays usage information.', negatable: false);
final argResults = argParser.parse(args);
if (argResults['help']) {
print(argParser.usage);
return;
}
runApp(Scroll.fromArgResults(argResults));
}
@immutable
class Scroll extends StatefulWidget {
final Duration samplingOffset;
final bool resample;
const Scroll(this.samplingOffset, {this.resample});
factory Scroll.fromArgResults(ArgResults parsed) {
return Scroll(
Duration(milliseconds: int.parse(parsed['sampling-offset-ms'])),
resample: parsed['resample']);
}
@override
State<StatefulWidget> createState() =>
ScrollState(samplingOffset, resample: resample);
}
class ScrollState extends State<Scroll> {
final Duration samplingOffset;
final bool resample;
ScrollController _controller;
SchedulerBinding _scheduler;
var _frameCallbackScheduled = false;
var _sampleTime = Duration();
var _lastPointerDataTimeStamp = Duration();
ui.PointerDataPacketCallback _callback;
final _resamplers = <int, PointerDataResampler>{};
ScrollState(this.samplingOffset, {this.resample});
void _onPointerDataPacket(ui.PointerDataPacket packet) {
Timeline.timeSync('onPointerDataPacket', () {
_lastPointerDataTimeStamp = packet.data.last.timeStamp;
for (var data in packet.data) {
if (resample && data.kind == ui.PointerDeviceKind.touch) {
var resampler = _resamplers.putIfAbsent(
data.device, () => PointerDataResampler());
var dataArguments = <String, int>{
'change': data.change.index,
'physicalX': data.physicalX.toInt(),
'physicalY': data.physicalY.toInt(),
};
Timeline.timeSync('addPointerData', () {
resampler.addData(data);
}, arguments: dataArguments);
_dispatchPointerData();
} else {
_dispatchPointerDataPacket(ui.PointerDataPacket(data: [data]));
}
}
});
}
void _dispatchPointerDataPacket(ui.PointerDataPacket packet) {
Timeline.timeSync('dispatchPointerDataPacket', () {
_callback(packet);
});
}
void _dispatchPointerData() {
for (var resampler in _resamplers.values) {
final packets = resampler.sample(_sampleTime);
if (packets.isNotEmpty) {
for (var data in packets) {
var dataArguments = <String, int>{
'change': data.change.index,
'physicalX': data.physicalX.toInt(),
'physicalY': data.physicalY.toInt(),
'physicalDeltaX': data.physicalDeltaX.toInt(),
'physicalDeltaY': data.physicalDeltaY.toInt(),
};
Timeline.timeSync('dispatchPointerData', () {},
arguments: dataArguments);
}
_dispatchPointerDataPacket(ui.PointerDataPacket(data: packets));
}
if (resampler.hasPendingData() || resampler.isTracked()) {
_scheduleFrameCallback();
}
}
}
void _scheduleFrameCallback() {
if (_frameCallbackScheduled) {
return;
}
_frameCallbackScheduled = true;
_scheduler.scheduleFrameCallback((_) {
_frameCallbackScheduled = false;
_sampleTime = _scheduler.currentSystemFrameTimeStamp + samplingOffset;
var frameArguments = <String, int>{
'frameTimeUs': _scheduler.currentSystemFrameTimeStamp.inMicroseconds,
'lastDataTimeStampUs': _lastPointerDataTimeStamp.inMicroseconds,
'timeStampMarginUs':
(_lastPointerDataTimeStamp - _sampleTime).inMicroseconds
};
Timeline.timeSync('onFrameCallback', _dispatchPointerData,
arguments: frameArguments);
});
}
@override
void initState() {
_controller = ScrollController(initialScrollOffset: 8000.0);
_scheduler = SchedulerBinding.instance;
_callback = ui.window.onPointerDataPacket;
ui.window.onPointerDataPacket = _onPointerDataPacket;
super.initState();
}
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: ui.TextDirection.ltr,
child: ConstrainedBox(
constraints: BoxConstraints.expand(),
child: Container(
color: Colors.white,
child: ListView(
controller: _controller,
children: <Widget>[
Container(height: 8192.0),
Center(
child: Text(
'Scroll Me!',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 32,
),
),
),
Container(height: 8192.0),
],
),
),
),
);
}
}