Hixie 74575775bd Introduce an explicit Key type.
This fixes some theoretical bugs whereby we were using hashCode to try
to get unique keys for objects, but really we wanted object identity.
It also lays the groundwork for a new GlobalKey concept.

I tried to keep the impact on the code minimal, which is why the "Key"
constructor is actually a factory that returns a StringKey. The code
has this class hierarchy:

```
   KeyBase
    |
   Key--------------+---------------+
    |               |               |
   StringKey    ObjectKey       UniqueKey
```

...where the constructors are Key and Key.stringify (StringKey),
Key.fromObjectIdentity (ObjectKey), and Key.unique (UniqueKey).

We could instead of factory methods use regular constructors with the
following hierarchy:

```
   KeyBase
    |
   LocalKey---------+---------------+
    |               |               |
   Key      ObjectIdentityKey   UniqueKey
```

...with constructors Key, Key.stringify, ObjectIdentityKey, and
UniqueKey, but I felt that that was maybe a more confusing hierarchy.
I don't have a strong opinion on this.
2015-07-22 09:14:06 -07:00

170 lines
4.2 KiB
Dart

// 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({
Key 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);
}
}