зеркало из https://github.com/mozilla/pjs.git
Bug 538682 - Skip frames during kinetic panning [r=mfinkle, froystig]
This commit is contained in:
Родитель
633aee553b
Коммит
b05c1daee3
|
@ -352,9 +352,10 @@ pref("dom.max_script_run_time", 20);
|
|||
pref("browser.console.showInPanel", false);
|
||||
|
||||
// kinetic tweakables
|
||||
pref("browser.ui.kinetic.updateInterval", 33);
|
||||
pref("browser.ui.kinetic.ema.alphaValue", 8);
|
||||
pref("browser.ui.kinetic.decelerationRate", 15);
|
||||
pref("browser.ui.kinetic.updateInterval", 30);
|
||||
pref("browser.ui.kinetic.decelerationRate", 20);
|
||||
pref("browser.ui.kinetic.speedSensitivity", 80);
|
||||
pref("browser.ui.kinetic.swipeLength", 160);
|
||||
|
||||
// Disable default plugin
|
||||
pref("plugin.default_plugin_disabled", true);
|
||||
|
|
|
@ -42,24 +42,12 @@
|
|||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
// how much movement input to take before mouse up for calculating kinetic speed
|
||||
const kSwipeLength = 160;
|
||||
|
||||
// how many msecs elapse before two taps are not a double tap
|
||||
const kDoubleClickInterval = 400;
|
||||
|
||||
// threshold in pixels for sensing a tap as opposed to a pan
|
||||
const kTapRadius = 25;
|
||||
|
||||
// how many milliseconds between each kinetic pan update
|
||||
const kKineticUpdateInterval = 25;
|
||||
|
||||
// How much speed is removed every update
|
||||
const kDecelerationRate = .09;
|
||||
|
||||
// How sensitive kinetic scroll is to mouse movement
|
||||
const kSpeedSensitivity = 1.1;
|
||||
|
||||
// Same as NS_EVENT_STATE_ACTIVE from nsIEventStateManager.h
|
||||
const kStateActive = 0x00000001;
|
||||
|
||||
|
@ -996,37 +984,33 @@ DragData.prototype = {
|
|||
* aPanBy is a function that will be called with the dx and dy
|
||||
* generated by the kinetic algorithm. It should return true if the
|
||||
* object was panned, false if there was no movement.
|
||||
*
|
||||
* There are two complicated things done here. One is calculating the
|
||||
* initial velocity of the movement based on user input. Two is
|
||||
* calculating the distance to move every frame.
|
||||
*/
|
||||
function KineticController(aPanBy, aEndCallback) {
|
||||
this._panBy = aPanBy;
|
||||
this._timer = null;
|
||||
this._beforeEnd = aEndCallback;
|
||||
|
||||
try {
|
||||
this._updateInterval = gPrefService.getIntPref("browser.ui.kinetic.updateInterval");
|
||||
} catch(e) {
|
||||
this._updateInterval = kKineticUpdateInterval;
|
||||
}
|
||||
// These are used to calculate the position of the scroll panes during kinetic panning. Think of
|
||||
// these points as vectors that are added together and multiplied by scalars.
|
||||
this._position = new Point(0, 0);
|
||||
this._velocity = new Point(0, 0);
|
||||
this._acceleration = new Point(0, 0);
|
||||
this._time = 0;
|
||||
this._timeStart = 0;
|
||||
|
||||
try {
|
||||
// In preferences this value is an int. We divide so that it can be a percent.
|
||||
this._decelerationRate = gPrefService.getIntPref("browser.ui.kinetic.decelerationRate") / 100;
|
||||
} catch (e) {
|
||||
this._decelerationRate = kDecelerationRate;
|
||||
};
|
||||
|
||||
try {
|
||||
// In preferences this value is an int. We divide so that it can be a percent.
|
||||
this._speedSensitivity = gPrefService.getIntPref("browser.ui.kinetic.speedsensitivity") / 100;
|
||||
} catch(e) {
|
||||
this._speedSensitivity = kSpeedSensitivity;
|
||||
}
|
||||
|
||||
try {
|
||||
this._swipeLength = gPrefService.getIntPref("browser.ui.kinetic.swipelength");
|
||||
} catch(e) {
|
||||
this._swipeLength = kSwipeLength;
|
||||
}
|
||||
// How often do we change the position of the scroll pane? Too often and panning may jerk near
|
||||
// the end. Too little and panning will be choppy. In milliseconds.
|
||||
this._updateInterval = gPrefService.getIntPref("browser.ui.kinetic.updateInterval");
|
||||
// "Friction" of the scroll pane. The lower, the less friction and the further distance traveled.
|
||||
this._decelerationRate = gPrefService.getIntPref("browser.ui.kinetic.decelerationRate") / 10000;
|
||||
// A multiplier for the initial velocity of the movement.
|
||||
this._speedSensitivity = gPrefService.getIntPref("browser.ui.kinetic.speedSensitivity") / 100;
|
||||
// Number of milliseconds that can contain a swipe. Movements earlier than this are disregarded.
|
||||
this._swipeLength = gPrefService.getIntPref("browser.ui.kinetic.swipeLength");
|
||||
|
||||
this._reset();
|
||||
}
|
||||
|
@ -1039,8 +1023,7 @@ KineticController.prototype = {
|
|||
}
|
||||
|
||||
this.momentumBuffer = [];
|
||||
this._speedX = 0;
|
||||
this._speedY = 0;
|
||||
this._velocity.set(0, 0);
|
||||
},
|
||||
|
||||
isActive: function isActive() {
|
||||
|
@ -1048,6 +1031,35 @@ KineticController.prototype = {
|
|||
},
|
||||
|
||||
_startTimer: function _startTimer() {
|
||||
// Use closed form of a parabola to calculate each position for panning.
|
||||
// x(t) = v0*t + .5*t^2*a
|
||||
// where: v0 is initial velocity
|
||||
// a is acceleration
|
||||
// t is time elapsed
|
||||
//
|
||||
// x(t)
|
||||
// ^
|
||||
// | |
|
||||
// |
|
||||
// | |
|
||||
// | ....^^^^....
|
||||
// | ...^^ | ^^...
|
||||
// | ...^ ^...
|
||||
// |.. | ..
|
||||
// -----------------------------------> t
|
||||
// t0 tf=-v0/a
|
||||
//
|
||||
// Using this formula, distance moved is independent of the time between each frame, unlike time
|
||||
// step approaches. Once the time is up, set the position to x(tf) and stop the timer.
|
||||
|
||||
let lastx = this._position; // track last position vector because pan takes differences
|
||||
let v0 = this._velocity; // initial velocity
|
||||
let a = this._acceleration; // acceleration
|
||||
|
||||
// Temporary "bins" so that we don't create new objects during pan.
|
||||
let aBin = new Point(0, 0);
|
||||
let v0Bin = new Point(0, 0);
|
||||
|
||||
let callback = {
|
||||
_self: this,
|
||||
notify: function kineticTimerCallback(timer) {
|
||||
|
@ -1056,34 +1068,40 @@ KineticController.prototype = {
|
|||
if (!self.isActive()) // someone called end() on us between timer intervals
|
||||
return;
|
||||
|
||||
//dump(" speeds: " + self._speedX + " " + self._speedY + "\n");
|
||||
// To make animation end fast enough but to keep smoothness, average the ideal
|
||||
// time frame (smooth animation) with the actual time lapse (end fast enough).
|
||||
// Animation will never take longer than 2 times the ideal length of time.
|
||||
let realt = Date.now() - self._initialTime;
|
||||
self._time += self._updateInterval;
|
||||
let t = (self._time + realt) / 2;
|
||||
|
||||
if (self._speedX == 0 && self._speedY == 0) {
|
||||
self.end();
|
||||
return;
|
||||
// Calculate new position using x(t) formula.
|
||||
let x = v0Bin.set(v0).scale(t).add(aBin.set(a).scale(0.5 * t * t));
|
||||
let dx = x.x - lastx.x;
|
||||
let dy = x.y - lastx.y;
|
||||
lastx.set(x);
|
||||
|
||||
// Test to see if movement is finished for each component. As seen in graph, we want the
|
||||
// final position to be at tf.
|
||||
if (t >= -v0.x / a.x) {
|
||||
// Plug in t=-v0/a into x(t) to get final position.
|
||||
dx = -v0.x * v0.x / 2 / a.x - lastx.x;
|
||||
// Reset components. Next frame: a's component will be 0 and t >= NaN will be false.
|
||||
lastx.x = 0;
|
||||
v0.x = 0;
|
||||
a.x = 0;
|
||||
}
|
||||
// Symmetric to above case.
|
||||
if (t >= -v0.y / a.y) {
|
||||
dy = -v0.y * v0.y / 2 / a.y - lastx.y;
|
||||
lastx.y = 0;
|
||||
v0.y = 0;
|
||||
a.y = 0;
|
||||
}
|
||||
let dx = Math.round(self._speedX * self._updateInterval);
|
||||
let dy = Math.round(self._speedY * self._updateInterval);
|
||||
|
||||
let panned = false;
|
||||
try { panned = self._panBy(-dx, -dy); } catch (e) {}
|
||||
if (!panned) {
|
||||
self.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (self._speedX < 0) {
|
||||
self._speedX = Math.min(self._speedX + self._decelerationRate, 0);
|
||||
} else if (self._speedX > 0) {
|
||||
self._speedX = Math.max(self._speedX - self._decelerationRate, 0);
|
||||
}
|
||||
if (self._speedY < 0) {
|
||||
self._speedY = Math.min(self._speedY + self._decelerationRate, 0);
|
||||
} else if (self._speedY > 0) {
|
||||
self._speedY = Math.max(self._speedY - self._decelerationRate, 0);
|
||||
}
|
||||
|
||||
if (self._speedX == 0 && self._speedY == 0)
|
||||
try { panned = self._panBy(Math.round(-dx), Math.round(-dy)); } catch (e) {}
|
||||
if (!panned)
|
||||
self.end();
|
||||
}
|
||||
};
|
||||
|
@ -1096,6 +1114,10 @@ KineticController.prototype = {
|
|||
},
|
||||
|
||||
start: function start() {
|
||||
function sign(x) {
|
||||
return x ? ((x > 0) ? 1 : -1) : 0;
|
||||
}
|
||||
|
||||
let mb = this.momentumBuffer;
|
||||
let mblen = this.momentumBuffer.length;
|
||||
|
||||
|
@ -1115,12 +1137,19 @@ KineticController.prototype = {
|
|||
}
|
||||
|
||||
// Only allow kinetic scrolling to speed up if kinetic scrolling is active.
|
||||
this._speedX = (distanceX < 0 ? Math.min : Math.max)((distanceX / swipeLength) * this._speedSensitivity, this._speedX);
|
||||
this._speedY = (distanceY < 0 ? Math.min : Math.max)((distanceY / swipeLength) * this._speedSensitivity, this._speedY);
|
||||
this._velocity.x = (distanceX < 0 ? Math.min : Math.max)((distanceX / swipeLength) * this._speedSensitivity, this._velocity.x);
|
||||
this._velocity.y = (distanceY < 0 ? Math.min : Math.max)((distanceY / swipeLength) * this._speedSensitivity, this._velocity.y);
|
||||
|
||||
// Set acceleration vector to opposite signs of velocity
|
||||
this._acceleration.set(this._velocity.clone().map(sign).scale(-this._decelerationRate));
|
||||
|
||||
this._position.set(0, 0);
|
||||
this._initialTime = Date.now();
|
||||
this._time = 0;
|
||||
this.momentumBuffer = [];
|
||||
if (!this.isActive()) {
|
||||
|
||||
if (!this.isActive())
|
||||
this._startTimer();
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
@ -1137,10 +1166,8 @@ KineticController.prototype = {
|
|||
|
||||
if (this.isActive()) {
|
||||
// Stop active movement when dragging in other direction.
|
||||
if (dx * this._speedX < 0)
|
||||
this._speedX = 0;
|
||||
if (dy * this._speedY < 0)
|
||||
this._speedY = 0;
|
||||
if (dx * this._velocity.x < 0 || dy * this._velocity.y < 0)
|
||||
this.end();
|
||||
}
|
||||
|
||||
this.momentumBuffer.push({'t': now, 'dx' : dx, 'dy' : dy});
|
||||
|
|
Загрузка…
Ссылка в новой задаче