UNPKG

ares-ide

Version:

A browser-based code editor and UI designer for Enyo 2 projects

311 lines (309 loc) 9.04 kB
/** _enyo.ScrollMath_ implements a scrolling dynamics simulation. It is a helper kind used by other scroller kinds, such as <a href="#enyo.TouchScrollStrategy">enyo.TouchScrollStrategy</a>. _enyo.ScrollMath_ is not typically created in application code. */ enyo.kind({ name: "enyo.ScrollMath", kind: "enyo.Component", published: { //* True if vertical scrolling is enabled vertical: true, //* True if horizontal scrolling is enabled horizontal: true }, events: { //* Fires when scroll action starts. onScrollStart: "", //* Fires while scroll action is in progress. onScroll: "", //* Fires when scroll action stops. onScrollStop: "" }, //* 'Spring' damping returns the scroll position to a value inside the //* boundaries. Lower values provide _faster_ snapback. kSpringDamping: 0.93, //* 'Drag' damping resists dragging the scroll position beyond the //* boundaries. Lower values provide _more_ resistance. kDragDamping: 0.5, //* 'Friction' damping reduces momentum over time. Lower values provide //* _more_ friction. kFrictionDamping: 0.97, //* Additional 'friction' damping applied when momentum carries the viewport //* into overscroll. Lower values provide _more_ friction. kSnapFriction: 0.9, //* Scalar applied to 'flick' event velocity kFlickScalar: 15, //* Limits the maximum allowable flick. On Android > 2, we limit this to //* prevent compositing artifacts. kMaxFlick: enyo.platform.android > 2 ? 2 : 1e9, //* The value used in friction() to determine if the delta (e.g., y - y0) is //* close enough to zero to consider as zero kFrictionEpsilon: 1e-2, //* Top snap boundary, generally 0 topBoundary: 0, //* Right snap boundary, generally (viewport width - content width) rightBoundary: 0, //* Bottom snap boundary, generally (viewport height - content height) bottomBoundary: 0, //* Left snap boundary, generally 0 leftBoundary: 0, //* Animation time step interval: 20, //* Flag to enable frame-based animation; if false, time-based animation is used fixedTime: true, //* @protected // simulation state x0: 0, x: 0, y0: 0, y: 0, destroy: enyo.inherit(function (sup) { return function() { this.stop(); sup.apply(this, arguments); }; }), /** Simple Verlet integrator for simulating Newtonian motion. */ verlet: function() { var x = this.x; this.x += x - this.x0; this.x0 = x; // var y = this.y; this.y += y - this.y0; this.y0 = y; }, /** Boundary damping function. Returns damped 'value' based on 'coeff' on one side of 'origin'. */ damping: function(value, origin, coeff, sign) { var kEpsilon = 0.5; // // this is basically just value *= coeff (generally, coeff < 1) // // 'sign' and the conditional is to force the damping to only occur // on one side of the origin. // var dv = value - origin; // Force close-to-zero to zero if (Math.abs(dv) < kEpsilon) { return origin; } return value*sign > origin*sign ? coeff * dv + origin : value; }, /** Dual-boundary damping function. Returns damped 'value' based on 'coeff' when exceeding either boundary. */ boundaryDamping: function(value, aBoundary, bBoundary, coeff) { return this.damping(this.damping(value, aBoundary, coeff, 1), bBoundary, coeff, -1); }, /** Simulation constraints (spring damping occurs here) */ constrain: function() { var y = this.boundaryDamping(this.y, this.topBoundary, this.bottomBoundary, this.kSpringDamping); if (y != this.y) { // ensure snapping introduces no velocity, add additional friction this.y0 = y - (this.y - this.y0) * this.kSnapFriction; this.y = y; } var x = this.boundaryDamping(this.x, this.leftBoundary, this.rightBoundary, this.kSpringDamping); if (x != this.x) { this.x0 = x - (this.x - this.x0) * this.kSnapFriction; this.x = x; } }, /** The friction function */ friction: function(inEx, inEx0, inCoeff) { // implicit velocity var dp = this[inEx] - this[inEx0]; // let close-to-zero collapse to zero (i.e. smaller than epsilon is considered zero) var c = Math.abs(dp) > this.kFrictionEpsilon ? inCoeff : 0; // reposition using damped velocity this[inEx] = this[inEx0] + c * dp; }, // one unit of time for simulation frame: 10, // piece-wise constraint simulation simulate: function(t) { while (t >= this.frame) { t -= this.frame; if (!this.dragging) { this.constrain(); } this.verlet(); this.friction('y', 'y0', this.kFrictionDamping); this.friction('x', 'x0', this.kFrictionDamping); } return t; }, animate: function() { this.stop(); // time tracking var t0 = enyo.now(), t = 0; // delta tracking var x0, y0; // animation handler var fn = this.bindSafely(function() { // wall-clock time var t1 = enyo.now(); // schedule next frame this.job = enyo.requestAnimationFrame(fn); // delta from last wall clock time var dt = t1 - t0; // record the time for next delta t0 = t1; // user drags override animation if (this.dragging) { this.y0 = this.y = this.uy; this.x0 = this.x = this.ux; } // frame-time accumulator // min acceptable time is 16ms (60fps) t += Math.max(16, dt); // alternate fixed-time step strategy: if (this.fixedTime && !this.isInOverScroll()) { t = this.interval; } // consume some t in simulation t = this.simulate(t); // scroll if we have moved, otherwise the animation is stalled and we can stop if (y0 != this.y || x0 != this.x) { //this.log(this.y, y0); this.scroll(); } else if (!this.dragging) { this.stop(true); this.scroll(); } y0 = this.y; x0 = this.x; }); this.job = enyo.requestAnimationFrame(fn); }, //* @protected start: function() { if (!this.job) { this.animate(); this.doScrollStart(); } }, stop: function(inFireEvent) { var job = this.job; if (job) { this.job = enyo.cancelRequestAnimationFrame(job); } if (inFireEvent) { this.doScrollStop(); } }, stabilize: function() { this.start(); var y = Math.min(this.topBoundary, Math.max(this.bottomBoundary, this.y)); var x = Math.min(this.leftBoundary, Math.max(this.rightBoundary, this.x)); this.y = this.y0 = y; this.x = this.x0 = x; this.scroll(); this.stop(true); }, startDrag: function(e) { this.dragging = true; // this.my = e.pageY; this.py = this.uy = this.y; // this.mx = e.pageX; this.px = this.ux = this.x; }, drag: function(e) { if (this.dragging) { var dy = this.vertical ? e.pageY - this.my : 0; this.uy = dy + this.py; // provides resistance against dragging into overscroll this.uy = this.boundaryDamping(this.uy, this.topBoundary, this.bottomBoundary, this.kDragDamping); // var dx = this.horizontal ? e.pageX - this.mx : 0; this.ux = dx + this.px; // provides resistance against dragging into overscroll this.ux = this.boundaryDamping(this.ux, this.leftBoundary, this.rightBoundary, this.kDragDamping); // this.start(); return true; } }, dragDrop: function() { if (this.dragging && !window.PalmSystem) { var kSimulatedFlickScalar = 0.5; this.y = this.uy; this.y0 = this.y - (this.y - this.y0) * kSimulatedFlickScalar; this.x = this.ux; this.x0 = this.x - (this.x - this.x0) * kSimulatedFlickScalar; } this.dragFinish(); }, dragFinish: function() { this.dragging = false; }, flick: function(e) { var v; if (this.vertical) { v = e.yVelocity > 0 ? Math.min(this.kMaxFlick, e.yVelocity) : Math.max(-this.kMaxFlick, e.yVelocity); this.y = this.y0 + v * this.kFlickScalar; } if (this.horizontal) { v = e.xVelocity > 0 ? Math.min(this.kMaxFlick, e.xVelocity) : Math.max(-this.kMaxFlick, e.xVelocity); this.x = this.x0 + v * this.kFlickScalar; } this.start(); }, mousewheel: function(e) { var dy = this.vertical ? e.wheelDeltaY || e.wheelDelta: 0; if ((dy > 0 && this.y < this.topBoundary) || (dy < 0 && this.y > this.bottomBoundary)) { this.stop(true); this.y = this.y0 = this.y0 + dy; this.start(); return true; } }, scroll: function() { this.doScroll(); }, // NOTE: Yip/Orvell method for determining scroller instantaneous velocity // FIXME: incorrect if called when scroller is in overscroll region // because does not account for additional overscroll damping. /** Animates a scroll to the specified position. */ scrollTo: function(inX, inY) { if (inY !== null) { this.y = this.y0 - (inY + this.y0) * (1 - this.kFrictionDamping); } if (inX !== null) { this.x = this.x0 - (inX + this.x0) * (1 - this.kFrictionDamping); } this.start(); }, setScrollX: function(inX) { this.x = this.x0 = inX; }, setScrollY: function(inY) { this.y = this.y0 = inY; }, setScrollPosition: function(inPosition) { this.setScrollY(inPosition); }, isScrolling: function() { return Boolean(this.job); }, isInOverScroll: function() { return this.job && (this.x > this.leftBoundary || this.x < this.rightBoundary || this.y > this.topBoundary || this.y < this.bottomBoundary); } });