|
|
@@ -30,6 +30,18 @@ var DragListener = FC.DragListener = Class.extend(ListenerMixin, {
|
|
|
shouldCancelTouchScroll: true,
|
|
|
scrollAlwaysKills: false,
|
|
|
|
|
|
+ isAutoScroll: false,
|
|
|
+
|
|
|
+ scrollBounds: null, // { top, bottom, left, right }
|
|
|
+ scrollTopVel: null, // pixels per second
|
|
|
+ scrollLeftVel: null, // pixels per second
|
|
|
+ scrollIntervalId: null, // ID of setTimeout for scrolling animation loop
|
|
|
+
|
|
|
+ // defaults
|
|
|
+ scrollSensitivity: 30, // pixels from edge for scrolling to start
|
|
|
+ scrollSpeed: 200, // pixels per second, at maximum speed
|
|
|
+ scrollIntervalMs: 50, // millisecond wait between scroll increment
|
|
|
+
|
|
|
|
|
|
constructor: function(options) {
|
|
|
this.options = options || {};
|
|
|
@@ -304,7 +316,177 @@ var DragListener = FC.DragListener = Class.extend(ListenerMixin, {
|
|
|
if (this['_' + name]) {
|
|
|
this['_' + name].apply(this, Array.prototype.slice.call(arguments, 1));
|
|
|
}
|
|
|
- }
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ // Auto-scroll
|
|
|
+ // -----------------------------------------------------------------------------------------------------------------
|
|
|
+
|
|
|
+
|
|
|
+ initAutoScroll: function() {
|
|
|
+ var scrollEl = this.scrollEl;
|
|
|
+
|
|
|
+ this.isAutoScroll =
|
|
|
+ this.options.scroll &&
|
|
|
+ scrollEl &&
|
|
|
+ !scrollEl.is(window) &&
|
|
|
+ !scrollEl.is(document);
|
|
|
+
|
|
|
+ if (this.isAutoScroll) {
|
|
|
+ // debounce makes sure rapid calls don't happen
|
|
|
+ this.listenTo(scrollEl, 'scroll', debounce(this.handleDebouncedScroll, 100));
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ destroyAutoScroll: function() {
|
|
|
+ this.endAutoScroll(); // kill any animation loop
|
|
|
+
|
|
|
+ // remove the scroll handler if there is a scrollEl
|
|
|
+ if (this.isAutoScroll) {
|
|
|
+ this.stopListeningTo(this.scrollEl, 'scroll'); // will probably get removed by unbindHandlers too :(
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ // Computes and stores the bounding rectangle of scrollEl
|
|
|
+ computeScrollBounds: function() {
|
|
|
+ if (this.isAutoScroll) {
|
|
|
+ this.scrollBounds = getOuterRect(this.scrollEl);
|
|
|
+ // TODO: use getClientRect in future. but prevents auto scrolling when on top of scrollbars
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ // Called when the dragging is in progress and scrolling should be updated
|
|
|
+ updateAutoScroll: function(ev) {
|
|
|
+ var sensitivity = this.scrollSensitivity;
|
|
|
+ var bounds = this.scrollBounds;
|
|
|
+ var topCloseness, bottomCloseness;
|
|
|
+ var leftCloseness, rightCloseness;
|
|
|
+ var topVel = 0;
|
|
|
+ var leftVel = 0;
|
|
|
+
|
|
|
+ if (bounds) { // only scroll if scrollEl exists
|
|
|
+
|
|
|
+ // compute closeness to edges. valid range is from 0.0 - 1.0
|
|
|
+ topCloseness = (sensitivity - (getEvY(ev) - bounds.top)) / sensitivity;
|
|
|
+ bottomCloseness = (sensitivity - (bounds.bottom - getEvY(ev))) / sensitivity;
|
|
|
+ leftCloseness = (sensitivity - (getEvX(ev) - bounds.left)) / sensitivity;
|
|
|
+ rightCloseness = (sensitivity - (bounds.right - getEvX(ev))) / sensitivity;
|
|
|
+
|
|
|
+ // translate vertical closeness into velocity.
|
|
|
+ // mouse must be completely in bounds for velocity to happen.
|
|
|
+ if (topCloseness >= 0 && topCloseness <= 1) {
|
|
|
+ topVel = topCloseness * this.scrollSpeed * -1; // negative. for scrolling up
|
|
|
+ }
|
|
|
+ else if (bottomCloseness >= 0 && bottomCloseness <= 1) {
|
|
|
+ topVel = bottomCloseness * this.scrollSpeed;
|
|
|
+ }
|
|
|
+
|
|
|
+ // translate horizontal closeness into velocity
|
|
|
+ if (leftCloseness >= 0 && leftCloseness <= 1) {
|
|
|
+ leftVel = leftCloseness * this.scrollSpeed * -1; // negative. for scrolling left
|
|
|
+ }
|
|
|
+ else if (rightCloseness >= 0 && rightCloseness <= 1) {
|
|
|
+ leftVel = rightCloseness * this.scrollSpeed;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this.setScrollVel(topVel, leftVel);
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ // Sets the speed-of-scrolling for the scrollEl
|
|
|
+ setScrollVel: function(topVel, leftVel) {
|
|
|
|
|
|
+ this.scrollTopVel = topVel;
|
|
|
+ this.scrollLeftVel = leftVel;
|
|
|
+
|
|
|
+ this.constrainScrollVel(); // massages into realistic values
|
|
|
+
|
|
|
+ // if there is non-zero velocity, and an animation loop hasn't already started, then START
|
|
|
+ if ((this.scrollTopVel || this.scrollLeftVel) && !this.scrollIntervalId) {
|
|
|
+ this.scrollIntervalId = setInterval(
|
|
|
+ proxy(this, 'scrollIntervalFunc'), // scope to `this`
|
|
|
+ this.scrollIntervalMs
|
|
|
+ );
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ // Forces scrollTopVel and scrollLeftVel to be zero if scrolling has already gone all the way
|
|
|
+ constrainScrollVel: function() {
|
|
|
+ var el = this.scrollEl;
|
|
|
+
|
|
|
+ if (this.scrollTopVel < 0) { // scrolling up?
|
|
|
+ if (el.scrollTop() <= 0) { // already scrolled all the way up?
|
|
|
+ this.scrollTopVel = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (this.scrollTopVel > 0) { // scrolling down?
|
|
|
+ if (el.scrollTop() + el[0].clientHeight >= el[0].scrollHeight) { // already scrolled all the way down?
|
|
|
+ this.scrollTopVel = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.scrollLeftVel < 0) { // scrolling left?
|
|
|
+ if (el.scrollLeft() <= 0) { // already scrolled all the left?
|
|
|
+ this.scrollLeftVel = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (this.scrollLeftVel > 0) { // scrolling right?
|
|
|
+ if (el.scrollLeft() + el[0].clientWidth >= el[0].scrollWidth) { // already scrolled all the way right?
|
|
|
+ this.scrollLeftVel = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ // This function gets called during every iteration of the scrolling animation loop
|
|
|
+ scrollIntervalFunc: function() {
|
|
|
+ var el = this.scrollEl;
|
|
|
+ var frac = this.scrollIntervalMs / 1000; // considering animation frequency, what the vel should be mult'd by
|
|
|
+
|
|
|
+ // change the value of scrollEl's scroll
|
|
|
+ if (this.scrollTopVel) {
|
|
|
+ el.scrollTop(el.scrollTop() + this.scrollTopVel * frac);
|
|
|
+ }
|
|
|
+ if (this.scrollLeftVel) {
|
|
|
+ el.scrollLeft(el.scrollLeft() + this.scrollLeftVel * frac);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.constrainScrollVel(); // since the scroll values changed, recompute the velocities
|
|
|
+
|
|
|
+ // if scrolled all the way, which causes the vels to be zero, stop the animation loop
|
|
|
+ if (!this.scrollTopVel && !this.scrollLeftVel) {
|
|
|
+ this.endAutoScroll();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ // Kills any existing scrolling animation loop
|
|
|
+ endAutoScroll: function() {
|
|
|
+ if (this.scrollIntervalId) {
|
|
|
+ clearInterval(this.scrollIntervalId);
|
|
|
+ this.scrollIntervalId = null;
|
|
|
+
|
|
|
+ this.handleScrollEnd();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ // Get called when the scrollEl is scrolled (NOTE: this is delayed via debounce)
|
|
|
+ handleDebouncedScroll: function() {
|
|
|
+ // recompute all coordinates, but *only* if this is *not* part of our scrolling animation
|
|
|
+ if (!this.scrollIntervalId) {
|
|
|
+ this.handleScrollEnd();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ // Called when scrolling has stopped, whether through auto scroll, or the user scrolling
|
|
|
+ handleScrollEnd: function() {
|
|
|
+ }
|
|
|
|
|
|
});
|