| // Copyright 2015 The Chromium 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:sky' as sky; |
| |
| import 'package:newton/newton.dart'; |
| import 'package:sky/animation/animated_simulation.dart'; |
| import 'package:sky/animation/scroll_behavior.dart'; |
| import 'package:sky/theme/view_configuration.dart' as config; |
| import 'package:sky/widgets/basic.dart'; |
| |
| const double _kMillisecondsPerSecond = 1000.0; |
| |
| double _velocityForFlingGesture(double eventVelocity) { |
| // eventVelocity is pixels/second, config min,max limits are pixels/ms |
| return eventVelocity.clamp(-config.kMaxFlingVelocity, config.kMaxFlingVelocity) / |
| _kMillisecondsPerSecond; |
| } |
| |
| abstract class ScrollClient { |
| bool ancestorScrolled(Scrollable ancestor); |
| } |
| |
| enum ScrollDirection { vertical, horizontal } |
| |
| abstract class Scrollable extends StatefulComponent { |
| |
| Scrollable({ |
| String key, |
| this.direction: ScrollDirection.vertical |
| }) : super(key: key); |
| |
| ScrollDirection direction; |
| |
| void initState() { |
| _animation = new AnimatedSimulation(_tickScrollOffset); |
| } |
| |
| void syncFields(Scrollable source) { |
| direction == source.direction; |
| } |
| |
| double _scrollOffset = 0.0; |
| double get scrollOffset => _scrollOffset; |
| |
| ScrollBehavior _scrollBehavior; |
| ScrollBehavior createScrollBehavior(); |
| ScrollBehavior get scrollBehavior { |
| if (_scrollBehavior == null) |
| _scrollBehavior = createScrollBehavior(); |
| return _scrollBehavior; |
| } |
| |
| AnimatedSimulation _animation; |
| |
| Widget buildContent(); |
| |
| Widget build() { |
| return new Listener( |
| child: buildContent(), |
| onPointerDown: _handlePointerDown, |
| onPointerUp: _handlePointerUpOrCancel, |
| onPointerCancel: _handlePointerUpOrCancel, |
| onGestureFlingStart: _handleFlingStart, |
| onGestureFlingCancel: _handleFlingCancel, |
| onGestureScrollUpdate: _handleScrollUpdate, |
| onWheel: _handleWheel |
| ); |
| } |
| |
| List<ScrollClient> _registeredScrollClients; |
| |
| void registerScrollClient(ScrollClient notifiee) { |
| if (_registeredScrollClients == null) |
| _registeredScrollClients = new List<ScrollClient>(); |
| setState(() { |
| _registeredScrollClients.add(notifiee); |
| }); |
| } |
| |
| void unregisterScrollClient(ScrollClient notifiee) { |
| if (_registeredScrollClients == null) |
| return; |
| setState(() { |
| _registeredScrollClients.remove(notifiee); |
| }); |
| } |
| |
| bool scrollTo(double newScrollOffset) { |
| if (newScrollOffset == _scrollOffset) |
| return false; |
| setState(() { |
| _scrollOffset = newScrollOffset; |
| }); |
| if (_registeredScrollClients != null) { |
| var newList = null; |
| _registeredScrollClients.forEach((target) { |
| if (target.ancestorScrolled(this)) { |
| if (newList == null) |
| newList = new List<ScrollClient>(); |
| newList.add(target); |
| } |
| }); |
| setState(() { |
| _registeredScrollClients = newList; |
| }); |
| } |
| return true; |
| } |
| |
| bool scrollBy(double scrollDelta) { |
| var newScrollOffset = scrollBehavior.applyCurve(_scrollOffset, scrollDelta); |
| return scrollTo(newScrollOffset); |
| } |
| |
| void didUnmount() { |
| _stopSimulation(); |
| super.didUnmount(); |
| } |
| |
| void settleScrollOffset() { |
| _startSimulation(); |
| } |
| |
| void _stopSimulation() { |
| _animation.stop(); |
| } |
| |
| void _startSimulation({ double velocity: 0.0 }) { |
| _stopSimulation(); |
| Simulation simulation = scrollBehavior.release(scrollOffset, velocity); |
| if (simulation != null) |
| _animation.start(simulation); |
| } |
| |
| void _tickScrollOffset(double value) { |
| scrollTo(value); |
| } |
| |
| void _handlePointerDown(_) { |
| _stopSimulation(); |
| } |
| |
| void _handlePointerUpOrCancel(_) { |
| if (!_animation.isAnimating) |
| settleScrollOffset(); |
| } |
| |
| void _handleScrollUpdate(sky.GestureEvent event) { |
| scrollBy(direction == ScrollDirection.horizontal ? event.dx : -event.dy); |
| } |
| |
| void _handleFlingStart(sky.GestureEvent event) { |
| double eventVelocity = direction == ScrollDirection.horizontal |
| ? -event.velocityX |
| : -event.velocityY; |
| _startSimulation(velocity: _velocityForFlingGesture(eventVelocity)); |
| } |
| |
| void _handleFlingCancel(sky.GestureEvent event) { |
| settleScrollOffset(); |
| } |
| |
| void _handleWheel(sky.WheelEvent event) { |
| scrollBy(-event.offsetY); |
| } |
| |
| } |