Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
1233 lines
37 KiB
JavaScript
1233 lines
37 KiB
JavaScript
'use strict';
|
|
/* global ScreenLayout */
|
|
|
|
/**
|
|
* This is one of possible LockScreen unlockers, and is the default unlocker
|
|
* would be used in Gaia. It's possible to follow the strategy pattern described
|
|
* in the LockSreen to make more unlockers.
|
|
*/
|
|
|
|
(function(exports) {
|
|
|
|
/**
|
|
* We should care about the need of testing,
|
|
* and make all stateful objects become instance-able.
|
|
*
|
|
* @param {Object} |opts| (Opional) addtional, options that may overwrite the
|
|
* default settings.
|
|
* The options should follow default settings above.
|
|
* @constructor
|
|
*/
|
|
var LockScreenSlide = function(opts) {
|
|
this.initialize(opts);
|
|
};
|
|
|
|
var LockScreenSlidePrototype = {
|
|
stopped: false,
|
|
canvas: null,
|
|
layout: '',
|
|
/**
|
|
* "new style" slider: as described in https://bugzil.la/950884
|
|
* if this is set true, slider will be rendered as specified there
|
|
* this module uses different render logics/data structures,
|
|
* when useNewStyle is set to true
|
|
* other callers of this module will see old-styled slider,
|
|
* when useNewStyle is not specified
|
|
*/
|
|
useNewStyle: false,
|
|
track: {
|
|
length: {tiny: '280', large: '410'},
|
|
color: 'rgba(255, 255, 255, 0.4)',
|
|
backgroundColor: 'rgba(0, 0, 0, 0)',
|
|
from: 0,
|
|
to: 0,
|
|
radius: 0,
|
|
width: 0 // We need dynamic length here.
|
|
},
|
|
|
|
trackNew: {
|
|
length: {tiny: '276', large: '406'},
|
|
strokeColorTop: 'rgba(0, 0, 0, 0.2)',
|
|
strokeColorBottom: 'rgba(0, 0, 0, 0)',
|
|
fillColorTop: 'rgba(0, 0, 0, 0.2)',
|
|
fillColorBottom: 'rgba(0, 0, 0, 0)',
|
|
backColor: 'rgba(51, 51, 51, 0.15)',
|
|
from: 0,
|
|
to: 0,
|
|
radius: 0,
|
|
width: 0 // We need dynamic length here.
|
|
},
|
|
|
|
arrows: {
|
|
left: null, right: null,
|
|
// Left and right drawing origin.
|
|
ldraw: {x: null, y: null},
|
|
rdraw: {x: null, y: null}
|
|
},
|
|
|
|
iconBG: {
|
|
radius: 20,
|
|
left: {
|
|
color: 'rgba(255, 255, 255, 0.25)'
|
|
},
|
|
right: {
|
|
color: 'rgba(255, 255, 255, 0.25)'
|
|
}
|
|
},
|
|
|
|
slides: {
|
|
left: null,
|
|
right: null
|
|
},
|
|
|
|
areas: {
|
|
left: null,
|
|
right: null
|
|
},
|
|
|
|
// The handle area can touch by the user.
|
|
area: null,
|
|
overlay: null,
|
|
|
|
width: 0, // We need dynamic length here.
|
|
height: 80,
|
|
center: {x: null, y: null},
|
|
|
|
handle: {
|
|
// Whether we need to auto extend the handle.
|
|
autoExpand: {
|
|
accState: 'normal', // In accelerating or not.
|
|
accFactor: 1.02, // Accelerate sliding (y = x^accFactor).
|
|
sentinelOffset: 40, // How many pixels before reaching end.
|
|
sentinelWidth: 0 // Max width - offset
|
|
},
|
|
bounceBackTime: 200, // ms
|
|
radius: 28, // The radius of the handle in pixel.
|
|
lineWidth: 1.6,
|
|
maxWidth: 0, // We need dynamic length here.
|
|
|
|
// The colors here is the current color, which
|
|
// will be alternated with another side's color
|
|
// when user dragged across the center.
|
|
|
|
// If it slide across the boundary to color it.
|
|
touchedColor: '0, 170, 204', // RGB
|
|
// The intermediate color of touched color.
|
|
touchedColorStop: '178, 229, 239',
|
|
// The initial stroke color.
|
|
color: '255, 255, 255',
|
|
// The initial handle background color.
|
|
backgroundColor: '255, 255, 255',
|
|
// The initial handle background color alpha value.
|
|
backgroundAlpha: 0
|
|
},
|
|
|
|
handleNew: {
|
|
// Whether we need to auto extend the handle.
|
|
autoExpand: {
|
|
accState: 'normal', // In accelerating or not.
|
|
accFactor: 1.02, // Accelerate sliding (y = x^accFactor).
|
|
sentinelOffset: 40, // How many pixels before reaching end.
|
|
sentinelWidth: 0 // Max width - offset
|
|
},
|
|
bounceBackTime: 200, // ms
|
|
radius: 30, // The radius of the handle in pixel.
|
|
innerRadius: 15,
|
|
outerColor: 'rgba(255, 255, 255, 0.9)',
|
|
innerColor: 'rgba(0, 0, 0, 0.05)',
|
|
lineWidth: 1.6,
|
|
maxWidth: 0, // We need dynamic length here.
|
|
towardLeft: false,
|
|
|
|
// The colors here is the current color, which
|
|
// will be alternated with another side's color
|
|
// when user dragged across the center.
|
|
|
|
// If it slide across the boundary to color it.
|
|
touchedColor: '0, 170, 204', // RGB
|
|
// The intermediate color of touched color.
|
|
touchedColorStop: '178, 229, 239'
|
|
},
|
|
|
|
colors: {
|
|
left: {
|
|
touchedColor: '255, 255, 255',
|
|
touchedColorStop: '255, 255, 255'
|
|
},
|
|
|
|
right: {
|
|
touchedColor: '255, 255, 255',
|
|
touchedColorStop: '255, 255, 255'
|
|
}
|
|
},
|
|
|
|
states: {
|
|
// Some elements can only be initialized after initialization...
|
|
initialized: false,
|
|
sliding: false,
|
|
slideReachEnd: false,
|
|
slidingColorful: false, // Start to color the handle.
|
|
slidingColorGradientEnd: false, // Full color the handle.
|
|
|
|
// Most of them need to be initialized later.
|
|
touch: {
|
|
id: '',
|
|
touched: false,
|
|
initX: -1,
|
|
pageX: -1,
|
|
pageY: -1,
|
|
tx: -1,
|
|
prevX: -1,
|
|
deltaX: 0 // Diff from prevX and current X
|
|
}
|
|
|
|
},
|
|
|
|
// How to get elements.
|
|
IDs: {
|
|
overlay: 'lockscreen',
|
|
area: 'lockscreen-area',
|
|
canvas: 'lockscreen-canvas',
|
|
areas: {
|
|
left: 'lockscreen-area-camera',
|
|
right: 'lockscreen-area-unlock'
|
|
}
|
|
},
|
|
|
|
// Paths to access the resources.
|
|
resources: {
|
|
larrow: '/lockscreen/style/images/larrow.png',
|
|
rarrow: '/lockscreen/style/images/rarrow.png'
|
|
},
|
|
|
|
resourcesNew: {
|
|
larrow: '/lockscreen/style/images/lockscreen_toggle_arrow_left.png',
|
|
rarrow: '/lockscreen/style/images/lockscreen_toggle_arrow_right.png'
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Initialize this unlocker strategy.
|
|
*
|
|
* @param {Object} |opts| (Opional) addtional, options that may overwrite the
|
|
* default settings.
|
|
* The options should follow default settings above.
|
|
* @this {LockScreenSlide}
|
|
*/
|
|
LockScreenSlidePrototype.initialize =
|
|
function(opts) {
|
|
if (opts) {
|
|
this._overwriteSettings(opts);
|
|
}
|
|
if (opts.useNewStyle) {
|
|
this.track = this.trackNew;
|
|
this.handle = this.handleNew;
|
|
this.resources = this.resourcesNew;
|
|
}
|
|
this._initializeCanvas();
|
|
this.publish('lockscreenslide-unlocker-initializer');
|
|
this.states.initialized = true;
|
|
};
|
|
|
|
LockScreenSlidePrototype.reset = function lss_reset() {
|
|
this._clearCanvas();
|
|
this._drawTrack();
|
|
this._resetHandle();
|
|
this._resetArrows();
|
|
this._drawIconBG();
|
|
};
|
|
|
|
/**
|
|
* Overwrite settings recursively.
|
|
*
|
|
* @param {Object} |options| options for overwrite default settings.
|
|
* @this {LockScreenSlide}
|
|
*/
|
|
LockScreenSlidePrototype._overwriteSettings =
|
|
function(options) {
|
|
var iterate = function _iterate(opts, settings) {
|
|
for (var property in opts) {
|
|
if (opts.hasOwnProperty(property)) {
|
|
if ('object' === typeof opts[property]) {
|
|
iterate(opts[property], settings[property]);
|
|
}
|
|
else {
|
|
settings[property] = opts[property];
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
iterate(options, this);
|
|
};
|
|
|
|
/**
|
|
* The dispatcher. Unlocker would manager all its DOMs individually.
|
|
*
|
|
* @param {event} |evt| LockScreen Slide event.
|
|
* @this {LockScreenSlide}
|
|
*/
|
|
LockScreenSlidePrototype.handleEvent =
|
|
function(evt) {
|
|
switch (evt.type) {
|
|
case 'screenchange':
|
|
// Don't lock if screen is turned off by promixity sensor.
|
|
if (evt.detail.screenOffBy == 'proximity') {
|
|
break;
|
|
}
|
|
|
|
// If the screen got blackout, we should restore the slide.
|
|
this._clearCanvas();
|
|
this._drawTrack();
|
|
this._resetHandle();
|
|
this._resetArrows();
|
|
this._drawIconBG();
|
|
break;
|
|
|
|
case 'touchstart':
|
|
evt.preventDefault();
|
|
if (evt.target !== this.area || evt.touches.length > 1) {
|
|
return;
|
|
}
|
|
this.states.touch.id = evt.touches[0].identifier;
|
|
this._onSlideBegin(this._dpx(evt.touches[0].pageX));
|
|
window.addEventListener('touchend', this);
|
|
window.addEventListener('touchmove', this);
|
|
break;
|
|
|
|
case 'touchmove':
|
|
// In order to prevent pocket unlocks we reset the slide progress and
|
|
// end the gesture detection if a new touch point appears on screen.
|
|
if (evt.touches.length > 1) {
|
|
this._endGesture();
|
|
return;
|
|
}
|
|
// Records touch states.
|
|
this._onTouchMove(
|
|
this._dpx(evt.touches[0].pageX),
|
|
this._dpx(evt.touches[0].pageY)
|
|
);
|
|
if (this.states.sliding) {
|
|
this._onSliding(this._dpx(evt.touches[0].pageX));
|
|
}
|
|
break;
|
|
|
|
case 'touchend':
|
|
if (evt.changedTouches[0].identifier !== this.states.touch.id) {
|
|
return;
|
|
}
|
|
|
|
this._endGesture();
|
|
break;
|
|
|
|
case 'click':
|
|
if (evt.mozInputSource === 0) {
|
|
evt.preventDefault();
|
|
if (this.areas.left === evt.target) {
|
|
this.publish('lockscreenslide-activate-left');
|
|
} else if (this.areas.right === evt.target) {
|
|
this.publish('lockscreenslide-activate-right');
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Initialize the canvas.
|
|
*
|
|
* @this {LockScreenSlide}
|
|
*/
|
|
LockScreenSlidePrototype._initializeCanvas =
|
|
function lss_initializeCanvas() {
|
|
|
|
this.overlay = document.getElementById(this.IDs.overlay);
|
|
this.area = document.getElementById(this.IDs.area);
|
|
this.canvas = document.getElementById(this.IDs.canvas);
|
|
this.areas.left = document.getElementById(this.IDs.areas.left);
|
|
this.areas.right = document.getElementById(this.IDs.areas.right);
|
|
|
|
this.area.addEventListener('touchstart', this);
|
|
this.areas.left.addEventListener('click', this);
|
|
this.areas.right.addEventListener('click', this);
|
|
|
|
// Capture the first overlay change and do the delayed initialization.
|
|
this.layout = (ScreenLayout && ScreenLayout.getCurrentLayout &&
|
|
ScreenLayout.getCurrentLayout()) ?
|
|
ScreenLayout.getCurrentLayout() : 'tiny';
|
|
|
|
var center = this.center;
|
|
this.arrows.left = new Image();
|
|
this.arrows.right = new Image();
|
|
var larrow = this.arrows.left;
|
|
var rarrow = this.arrows.right;
|
|
larrow.src = this.resources.larrow;
|
|
rarrow.src = this.resources.rarrow;
|
|
|
|
// XXX: Bet it would be OK while user start to drag the slide.
|
|
larrow.onload = (function() {
|
|
var offset =
|
|
this.useNewStyle ?
|
|
(this.arrows.left.width + this.handle.radius) :
|
|
(this.arrows.left.width << 1);
|
|
this.arrows.ldraw.x =
|
|
center.x - offset;
|
|
this.arrows.ldraw.y =
|
|
center.y - (this.arrows.left.height >> 1);
|
|
var ctx = this.canvas.getContext('2d');
|
|
ctx.drawImage(this.arrows.left,
|
|
this.arrows.ldraw.x,
|
|
this.arrows.ldraw.y,
|
|
this.arrows.left.width,
|
|
this.arrows.left.height);
|
|
}).bind(this);
|
|
rarrow.onload = (function() {
|
|
var offset =
|
|
this.useNewStyle ? this.handle.radius : this.arrows.right.width;
|
|
this.arrows.rdraw.x =
|
|
center.x + offset;
|
|
this.arrows.rdraw.y =
|
|
center.y - (this.arrows.right.height >> 1);
|
|
var ctx = this.canvas.getContext('2d');
|
|
ctx.drawImage(this.arrows.right,
|
|
this.arrows.rdraw.x,
|
|
this.arrows.rdraw.y,
|
|
this.arrows.right.width,
|
|
this.arrows.right.height);
|
|
}).bind(this);
|
|
|
|
this.width = this._dpx(window.innerWidth);
|
|
this.height = this._dpx(80);
|
|
|
|
this.canvas.width = this.width;
|
|
this.canvas.height = this.height;
|
|
|
|
// Shrink the canvas back to keep the density.
|
|
this.canvas.style.width = window.innerWidth + 'px';
|
|
this.canvas.style.height = 80 + 'px';
|
|
|
|
this.center.x =
|
|
this.canvas.offsetLeft + this.canvas.width >> 1;
|
|
this.center.y =
|
|
this.canvas.offsetTop + this.canvas.height >> 1;
|
|
|
|
this.handle.radius =
|
|
this._dpx(this.handle.radius);
|
|
|
|
if (this.useNewStyle) {
|
|
this.handle.innerRadius =
|
|
this._dpx(this.handle.innerRadius);
|
|
}
|
|
|
|
this.track.radius =
|
|
this.handle.radius + this._dpx(this.useNewStyle ? 2 : 1);
|
|
|
|
this.handle.lineWidth =
|
|
this._dpx(this.handle.lineWidth);
|
|
|
|
this.handle.autoExpand.sentinelOffset =
|
|
this._dpx(this.handle.autoExpand.sentinelOffset);
|
|
|
|
this.iconBG.radius = this._dpx(this.iconBG.radius);
|
|
|
|
this.canvas.getContext('2d').save();
|
|
|
|
// Need to move the context toward right, to compensate the circle which
|
|
// would be draw at the center, and make it align too left.
|
|
this.canvas.getContext('2d', this.handle.radius << 1, 0);
|
|
|
|
// We don't reset the arrows because it need to be draw while image
|
|
// got loaded, which is a asynchronous process.
|
|
|
|
var trackLength = 'tiny' === this.layout ?
|
|
this.track.length.tiny : this.track.length.large;
|
|
|
|
// Offset and clientWidth would be window size.
|
|
trackLength = this._dpx(trackLength);
|
|
|
|
// Because the canvas would draw from the center to one point
|
|
// on the circle, it would add dimeter long distance for one side.
|
|
var maxWidth = (trackLength -
|
|
(this.handle.radius << 1)) >> 1;
|
|
|
|
// Left 1 pixel each side for the border.
|
|
maxWidth -= 2;
|
|
this.handle.maxWidth = maxWidth;
|
|
this.handle.autoExpand.sentinelWidth =
|
|
maxWidth - this.handle.autoExpand.sentinelOffset;
|
|
|
|
this.track.width = trackLength;
|
|
this.track.from = this.center.x - maxWidth;
|
|
this.track.to = this.center.x + maxWidth;
|
|
this.track.y = this.center.y;
|
|
|
|
this._drawTrack();
|
|
this._drawIconBG();
|
|
|
|
// Draw the handle.
|
|
this._resetHandle();
|
|
this._resetTouchStates();
|
|
};
|
|
|
|
/**
|
|
* Finalize the canvas: restore its default states.
|
|
*
|
|
* @this {LockScreenSlide}
|
|
*/
|
|
LockScreenSlidePrototype._finalizeCanvas =
|
|
function lss_finalizeCanvas() {
|
|
this.states.slidingColorful = false;
|
|
this.states.slidingColorGradientEnd = false,
|
|
this._clearCanvas();
|
|
};
|
|
|
|
/**
|
|
* Records how long the user's finger dragged.
|
|
*
|
|
* @param {number} |tx| The absolute coordinate of the touching position.
|
|
* @this {LockScreenSlide}
|
|
*/
|
|
LockScreenSlidePrototype._onSliding =
|
|
function lss_onSliding(tx) {
|
|
var mtx = this._mapCoord(tx, 0)[0];
|
|
var isLeft = tx - this.center.x < 0;
|
|
var prevState;
|
|
var currentState;
|
|
this._clearCanvas();
|
|
|
|
var expandSentinelR = this.center.x +
|
|
this.handle.autoExpand.sentinelWidth;
|
|
|
|
var expandSentinelL = this.center.x -
|
|
this.handle.autoExpand.sentinelWidth;
|
|
|
|
if (tx > expandSentinelR || tx < expandSentinelL) {
|
|
prevState = this.handle.autoExpand.accState;
|
|
this.handle.autoExpand.accState = 'accelerating';
|
|
currentState = this.handle.autoExpand.accState;
|
|
var slow = false;
|
|
if (isLeft) {
|
|
slow = this.states.touch.deltaX > 0;
|
|
if (prevState !== currentState) {
|
|
this.publish('lockscreenslide-near-left',
|
|
{'currentState': currentState, 'prevState': prevState});
|
|
}
|
|
} else {
|
|
slow = this.states.touch.deltaX < 0;
|
|
if (prevState !== currentState) {
|
|
this.publish('lockscreenslide-near-right',
|
|
{'currentState': currentState, 'prevState': prevState});
|
|
}
|
|
}
|
|
} else {
|
|
prevState = this.handle.autoExpand.accState;
|
|
this.handle.autoExpand.accState = 'normal';
|
|
currentState = this.handle.autoExpand.accState;
|
|
if (prevState !== currentState) {
|
|
if (isLeft) {
|
|
if (prevState !== currentState) {
|
|
this.publish('lockscreenslide-near-left',
|
|
{'currentState': currentState, 'prevState': prevState});
|
|
}
|
|
} else {
|
|
if (prevState !== currentState) {
|
|
this.publish('lockscreenslide-near-right',
|
|
{'currentState': currentState, 'prevState': prevState});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
mtx = this._accelerateSlide(tx);
|
|
|
|
// Order matters.
|
|
this._drawTrack();
|
|
this._drawArrowsTo(mtx);
|
|
this._drawIconBG();
|
|
this._drawSlideTo(mtx);
|
|
};
|
|
|
|
/**
|
|
* Start slide the handle of the lockscreen.
|
|
* Effect: Will set touch and sliding flag in this.
|
|
*
|
|
* @param {tx} The absolute coordinate X of the touch position.
|
|
* @this {LockScreenSlide}
|
|
*/
|
|
LockScreenSlidePrototype._onSlideBegin =
|
|
function lss_onSlideBegin(tx) {
|
|
var center = this.center;
|
|
|
|
// To see if the finger touch on the area of the center circle.
|
|
var boundaryR = center.x + this.handle.radius;
|
|
var boundaryL = center.x - this.handle.radius;
|
|
|
|
if (tx > boundaryR || tx < boundaryL) {
|
|
this.states.sliding = false;
|
|
return; // Do nothing.
|
|
}
|
|
|
|
this.publish('lockscreenslide-unlocking-start');
|
|
this.states.touch.initX = tx;
|
|
this.states.sliding = true;
|
|
this._lightIcons();
|
|
};
|
|
|
|
/**
|
|
* Encapsulating all the cleanups needed at the end of a gesture.
|
|
*
|
|
* @this {LockScreenSlide}
|
|
*/
|
|
LockScreenSlidePrototype._endGesture =
|
|
function lss_endGesture() {
|
|
window.removeEventListener('touchmove', this);
|
|
window.removeEventListener('touchend', this);
|
|
|
|
this.states.sliding = false;
|
|
this._onSlideEnd();
|
|
this._resetTouchStates();
|
|
this.overlay.classList.remove('touched');
|
|
this.states.slideReachEnd = false;
|
|
};
|
|
|
|
LockScreenSlidePrototype._stop =
|
|
function lss_stop() {
|
|
if (this._stopped) { return; }
|
|
window.removeEventListener('touchmove', this);
|
|
window.removeEventListener('touchend', this);
|
|
this.area.removeEventListener('touchstart', this);
|
|
this._stopped = true;
|
|
};
|
|
|
|
LockScreenSlidePrototype._start =
|
|
function lss_stop() {
|
|
if (!this._stopped) { return; }
|
|
window.addEventListener('touchmove', this);
|
|
window.addEventListener('touchend', this);
|
|
this.area.addEventListener('touchstart', this);
|
|
this._stopped = false;
|
|
};
|
|
|
|
/**
|
|
* When user released the finger, bounce it back.
|
|
*
|
|
* @this {LockScreenSlide}
|
|
*/
|
|
LockScreenSlidePrototype._onSlideEnd =
|
|
function lss_onSlideEnd() {
|
|
|
|
var isLeft = this.states.touch.pageX - this.center.x < 0;
|
|
if (false === this.states.slideReachEnd) {
|
|
this._bounceBack(this.states.touch.pageX);
|
|
} else {
|
|
var intention = isLeft ? 'lockscreenslide-activate-left' :
|
|
'lockscreenslide-activate-right';
|
|
this.publish(intention);
|
|
}
|
|
|
|
this.publish('lockscreenslide-unlocking-stop');
|
|
this._darkIcons();
|
|
};
|
|
|
|
/**
|
|
* When touchmove event on, records the information.
|
|
*
|
|
* @param {number} |pageX| The absolute coordinate X of the finger.
|
|
* @param {number} |pageY| The absolute coordinate Y of the finger.
|
|
* @this {LockScreenSlide}
|
|
*/
|
|
LockScreenSlidePrototype._onTouchMove =
|
|
function ls_handleMove(pageX, pageY) {
|
|
var touch = this.states.touch;
|
|
|
|
if (!touch.touched) {
|
|
|
|
// Do nothing if the user have not move the finger to the slide yet.
|
|
if (!this.states.sliding) {
|
|
return;
|
|
}
|
|
|
|
touch.touched = true;
|
|
touch.initX = pageX;
|
|
|
|
var overlay = this.overlay;
|
|
overlay.classList.add('touched');
|
|
}
|
|
|
|
touch.tx = pageX - touch.initX;
|
|
touch.pageX = pageX;
|
|
|
|
if (-1 !== touch.pageX) {
|
|
touch.deltaX = pageX - touch.prevX;
|
|
}
|
|
|
|
touch.prevX = pageX;
|
|
};
|
|
|
|
/**
|
|
* Accelerate the slide when the finger is near the end.
|
|
*
|
|
* @param {number} |tx| The absolute coordinate of the touching position.
|
|
* @return {number}
|
|
* @this {LockScreenSlide}
|
|
*/
|
|
LockScreenSlidePrototype._accelerateSlide =
|
|
function lss_accelerateSlide(tx) {
|
|
var isLeft = tx - this.center.x < 0;
|
|
var dx = Math.abs(tx - this.center.x);
|
|
var accFactor = this.handle.autoExpand.accFactor;
|
|
var acc = Math.pow(dx, accFactor);
|
|
var accTx = tx + acc;
|
|
if (isLeft) {
|
|
accTx = tx - acc;
|
|
}
|
|
|
|
if (accTx < 0) {
|
|
accTx = 0;
|
|
}
|
|
return accTx;
|
|
};
|
|
|
|
/**
|
|
* Clear the canvas.
|
|
*
|
|
* @this {LockScreenSlide}
|
|
*/
|
|
LockScreenSlidePrototype._clearCanvas =
|
|
function lss_clearCanvas() {
|
|
var canvas = this.canvas;
|
|
var ctx = canvas.getContext('2d');
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
};
|
|
|
|
/**
|
|
* Bounce the handle back from the |tx|.
|
|
*
|
|
* @param {number} |tx| The absolute horizontal position of the finger.
|
|
* @param {Function()} |cb| (Optional) Callback. Will be executed after
|
|
* the animation ended.
|
|
*
|
|
* @this {LockScreenSlide}
|
|
*/
|
|
LockScreenSlidePrototype._bounceBack =
|
|
function lss_bounceBack(tx, cb) {
|
|
// Absolute coordinate of the canvas center.
|
|
var duration = this.handle.bounceBackTime;
|
|
var center = this.center;
|
|
var nextTx = tx;
|
|
var tsBegin = null;
|
|
var mspf = 0; // ms per frame.
|
|
var interval = 1; // How many pixels per frame should draw.
|
|
// Draw from the circle center to one end on the circle itself.
|
|
var isLeft = tx - center.x < 0;
|
|
|
|
var drawIt = (function _drawIt(ts) {
|
|
if (null === tsBegin) {
|
|
tsBegin = ts;
|
|
}
|
|
|
|
if (ts - tsBegin < duration) {
|
|
if (0 === mspf) {
|
|
mspf = ts - tsBegin; // Not an accurate way to determine mspf.
|
|
}
|
|
interval = Math.abs(center.x - tx) / (duration / mspf);
|
|
nextTx = isLeft ? nextTx + interval : nextTx - interval;
|
|
if ((isLeft && nextTx < center.x) ||
|
|
(!isLeft && nextTx >= center.x)) {
|
|
this._clearCanvas();
|
|
this._drawTrack();
|
|
this._drawArrowsTo(nextTx);
|
|
this._drawIconBG();
|
|
this._drawSlideTo(nextTx);
|
|
}
|
|
requestAnimationFrame(drawIt);
|
|
} else {
|
|
// Compensation from the current position to the center of the slide.
|
|
this._clearCanvas();
|
|
this._drawTrack();
|
|
this._drawArrowsTo(center.x);
|
|
this._drawIconBG();
|
|
this._drawSlideTo(center.x);
|
|
if (cb) {
|
|
cb();
|
|
}
|
|
}
|
|
}).bind(this);
|
|
requestAnimationFrame(drawIt);
|
|
};
|
|
|
|
/**
|
|
* Dark the left and right icons when user leave our LockScreen.
|
|
*
|
|
* @this {LockScreenSlide}
|
|
*/
|
|
LockScreenSlidePrototype._darkIcons =
|
|
function lss_darkIcons() {
|
|
this.areas.left.classList.add('dark');
|
|
this.areas.right.classList.add('dark');
|
|
};
|
|
|
|
/**
|
|
* Draw the two arrows on the slide.
|
|
*
|
|
* @param {number} |tx| The absolute horizontal position of the target.
|
|
* @this {LockScreenSlide}
|
|
*/
|
|
LockScreenSlidePrototype._drawArrowsTo =
|
|
function lss_drawArrows(tx) {
|
|
var canvas = this.canvas;
|
|
var ctx = canvas.getContext('2d');
|
|
var center = this.center;
|
|
var offset = tx - center.x;
|
|
var isLeft = offset < 0;
|
|
var alpha = 1 - Math.min(1, Math.abs(offset) / this._dpx(30));
|
|
var position;
|
|
var oldAlpha;
|
|
|
|
if (this.handle.maxWidth < Math.abs(offset)) {
|
|
this.states.slideReachEnd = true;
|
|
return;
|
|
}
|
|
this.states.slideReachEnd = false;
|
|
|
|
// The Y of arrows: need to put it from center to sink half of the arrow.
|
|
if (isLeft) {
|
|
// XXX:<<1: OK but don't know why!
|
|
position =
|
|
this.useNewStyle ?
|
|
(tx - this.arrows.left.width - this.handle.radius) :
|
|
(tx - (this.arrows.left.width << 1));
|
|
oldAlpha = ctx.globalAlpha;
|
|
ctx.globalAlpha = alpha;
|
|
ctx.drawImage(this.arrows.left,
|
|
position,
|
|
this.arrows.ldraw.y,
|
|
this.arrows.left.width,
|
|
this.arrows.left.height);
|
|
ctx.globalAlpha = oldAlpha;
|
|
ctx.drawImage(this.arrows.right,
|
|
this.arrows.rdraw.x,
|
|
this.arrows.rdraw.y,
|
|
this.arrows.right.width,
|
|
this.arrows.right.height);
|
|
|
|
} else {
|
|
position =
|
|
this.useNewStyle ?
|
|
(tx + this.handle.radius) :
|
|
(tx + this.arrows.right.width);
|
|
oldAlpha = ctx.globalAlpha;
|
|
ctx.globalAlpha = alpha;
|
|
ctx.drawImage(this.arrows.right,
|
|
position,
|
|
this.arrows.rdraw.y,
|
|
this.arrows.right.width,
|
|
this.arrows.right.height);
|
|
ctx.globalAlpha = oldAlpha;
|
|
ctx.drawImage(this.arrows.left,
|
|
this.arrows.ldraw.x,
|
|
this.arrows.ldraw.y,
|
|
this.arrows.left.width,
|
|
this.arrows.left.height);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Draw the track of the slide.
|
|
*
|
|
* @this {LockScreenSlide}
|
|
*/
|
|
LockScreenSlidePrototype._drawTrack =
|
|
function lss_drawTrack() {
|
|
var canvas = this.canvas;
|
|
var ctx = canvas.getContext('2d');
|
|
|
|
var radius = this.track.radius;
|
|
var startAngle;
|
|
var endAngle;
|
|
|
|
if (this.useNewStyle) {
|
|
startAngle = 1.5 * Math.PI;
|
|
endAngle = 0.5 * Math.PI;
|
|
|
|
var draw = (function(fillStyle, strokeStyle) {
|
|
ctx.beginPath();
|
|
ctx.fillStyle = fillStyle;
|
|
ctx.strokeStyle = strokeStyle;
|
|
ctx.lineWidth = this.handle.lineWidth;
|
|
ctx.moveTo(this.track.from, this.track.y - radius);
|
|
ctx.lineTo(this.track.to, this.track.y - radius);
|
|
ctx.arc(this.track.to, this.track.y,
|
|
radius, startAngle, endAngle, false);
|
|
ctx.lineTo(this.track.from, this.track.y + radius);
|
|
ctx.arc(this.track.from, this.track.y,
|
|
radius, endAngle, startAngle, false);
|
|
ctx.fill();
|
|
ctx.closePath();
|
|
ctx.stroke();
|
|
}).bind(this);
|
|
|
|
// single-color background
|
|
draw(this.track.backColor, 'transparent');
|
|
|
|
// actual gradient
|
|
var gradientStroke =
|
|
ctx.createLinearGradient(
|
|
this.track.from - radius,
|
|
this.track.y - radius,
|
|
this.track.from - radius,
|
|
this.track.y + radius
|
|
);
|
|
var gradientFill =
|
|
ctx.createLinearGradient(
|
|
this.track.from - radius,
|
|
this.track.y - radius,
|
|
this.track.from - radius,
|
|
this.track.y + radius
|
|
);
|
|
gradientStroke.addColorStop(0, this.track.strokeColorTop);
|
|
gradientStroke.addColorStop(1, this.track.strokeColorBottom);
|
|
gradientFill.addColorStop(0, this.track.fillColorTop);
|
|
gradientFill.addColorStop(1, this.track.fillColorBottom);
|
|
|
|
draw(gradientFill, gradientStroke);
|
|
|
|
} else {
|
|
// 1.5 ~ 0.5 is the right part of a circle.
|
|
startAngle = 1.5 * Math.PI;
|
|
endAngle = 0.5 * Math.PI;
|
|
var strokeStyle = this.track.color;
|
|
|
|
ctx.fillStyle = this.track.backgroundColor;
|
|
ctx.lineWidth = this.handle.lineWidth;
|
|
ctx.strokeStyle = strokeStyle;
|
|
|
|
// Start to draw it.
|
|
// Can't use functions like rect or these individual parts
|
|
// would show its borders.
|
|
ctx.beginPath();
|
|
|
|
ctx.arc(this.track.from, this.track.y,
|
|
radius, endAngle, startAngle, false);
|
|
ctx.lineTo(this.track.from, this.track.y - radius);
|
|
ctx.lineTo(this.track.to, this.track.y - radius);
|
|
ctx.arc(this.track.to, this.track.y,
|
|
radius, startAngle, endAngle, false);
|
|
ctx.lineTo(this.track.from, this.track.y + radius);
|
|
|
|
ctx.fill();
|
|
ctx.stroke();
|
|
ctx.closePath();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Extend the slide from its center to the specific position.
|
|
*
|
|
* @param {number} |tx| The absolute horizontal position of the target.
|
|
* @this {LockScreenSlide}
|
|
*/
|
|
LockScreenSlidePrototype._drawSlideTo =
|
|
function lss_drawSlideTo(tx) {
|
|
var canvas = this.canvas;
|
|
var ctx = canvas.getContext('2d');
|
|
var maxWidth = this.handle.maxWidth;
|
|
|
|
var offset = tx;
|
|
var radius = this.handle.radius;
|
|
var center = this.center;
|
|
|
|
// The width and height of the rectangle.
|
|
var rw = offset - center.x;
|
|
var urw = Math.abs(rw);
|
|
|
|
if (this.handle.maxWidth < urw) {
|
|
offset = rw > 0 ? center.x + maxWidth : center.x - maxWidth;
|
|
}
|
|
|
|
var counterclock = false;
|
|
if (offset - center.x < 0) {
|
|
counterclock = true;
|
|
}
|
|
var isLeft = counterclock;
|
|
|
|
var isRight = offset - center.x > 0;
|
|
|
|
if (isLeft) {
|
|
this.handle.touchedColor = this.colors.left.touchedColor;
|
|
this.handle.touchedColorStop = this.colors.left.touchedColorStop;
|
|
} else if (isRight) {
|
|
this.handle.touchedColor = this.colors.right.touchedColor;
|
|
this.handle.touchedColorStop = this.colors.right.touchedColorStop;
|
|
}
|
|
|
|
// 1.5 ~ 0.5 is the right part of a circle.
|
|
var startAngle = 1.5 * Math.PI;
|
|
var endAngle = 0.5 * Math.PI;
|
|
var fillAlpha = 0.0;
|
|
var strokeStyle = 'white';
|
|
const GRADIENT_LENGTH = 50;
|
|
|
|
if (this.useNewStyle) {
|
|
if (0 === urw) { // Draw as the initial circle.
|
|
// outer circle
|
|
ctx.beginPath();
|
|
|
|
ctx.arc(center.x, center.y,
|
|
radius, 0, 2 * Math.PI, false);
|
|
ctx.fillStyle = this.handle.outerColor;
|
|
|
|
// Note: When setting both the fill and stroke for a shape,
|
|
// make sure that you use fill() before stroke().
|
|
// Otherwise, the fill will overlap half of the stroke.
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
// outer circle
|
|
ctx.beginPath();
|
|
|
|
ctx.arc(center.x, center.y,
|
|
this.handle.innerRadius, 0, 2 * Math.PI, false);
|
|
ctx.fillStyle = this.handle.innerColor;
|
|
|
|
// Note: When setting both the fill and stroke for a shape,
|
|
// make sure that you use fill() before stroke().
|
|
// Otherwise, the fill will overlap half of the stroke.
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
} else {
|
|
// from 0.9 to 0.2
|
|
fillAlpha = 0.9 - Math.min(0.7, (urw / this.handle.maxWidth) * 0.7);
|
|
|
|
strokeStyle = 'transparent';
|
|
|
|
ctx.fillStyle = 'rgba(' + this.handle.touchedColor + ', ' +
|
|
fillAlpha + ')';
|
|
ctx.strokeStyle = strokeStyle;
|
|
|
|
// Start to draw it.
|
|
// Can't use functions like rect or these individual parts
|
|
// would show its borders.
|
|
ctx.beginPath();
|
|
|
|
ctx.arc(center.x, center.y,
|
|
radius, endAngle, startAngle, counterclock);
|
|
ctx.lineTo(center.x, center.y - radius);
|
|
ctx.lineTo(center.x + (offset - center.x), center.y - radius);
|
|
ctx.arc(offset, center.y, radius, startAngle, endAngle, counterclock);
|
|
ctx.lineTo(center.x, center.y + radius);
|
|
|
|
// Note: When setting both the fill and stroke for a shape,
|
|
// make sure that you use fill() before stroke().
|
|
// Otherwise, the fill will overlap half of the stroke.
|
|
ctx.fill();
|
|
ctx.stroke();
|
|
ctx.closePath();
|
|
}
|
|
} else { // old style
|
|
var fillColor;
|
|
var strokeColor;
|
|
var borderAlpha;
|
|
// If user move over 15px, fill the slide.
|
|
if (urw > 15 && true !== this.states.slidingColorful) {
|
|
// The color should be gradient in this length, from the origin.
|
|
// It would decide how long the color turning to the touched color.
|
|
|
|
fillAlpha = (urw - 15) / GRADIENT_LENGTH;
|
|
if (fillAlpha > 1.0) {
|
|
fillAlpha = 1.0;
|
|
this.states.slidingColorGradientEnd = true;
|
|
}
|
|
|
|
// The border must disappear during the sliding,
|
|
// so it's alpha would decrease to zero.
|
|
borderAlpha = 1.0 - fillAlpha;
|
|
|
|
// From white to covered color.
|
|
strokeStyle = 'rgba(' + this.handle.touchedColorStop +
|
|
',' + borderAlpha + ')';
|
|
|
|
// It's colorful now.
|
|
this.states.slidingColorful = true;
|
|
} else {
|
|
|
|
if (0 === urw) { // Draw as the initial circle.
|
|
fillAlpha = this.handle.backgroundAlpha;
|
|
strokeColor = this.handle.color;
|
|
fillColor = this.handle.backgroundColor;
|
|
} else {
|
|
fillAlpha = (urw - 15) / GRADIENT_LENGTH;
|
|
if (fillAlpha > 1.0) {
|
|
fillAlpha = 1.0;
|
|
}
|
|
strokeColor = this.handle.touchedColorStop;
|
|
fillColor = this.handle.touchedColor;
|
|
}
|
|
borderAlpha = 1.0 - fillAlpha;
|
|
strokeStyle = 'rgba(' + strokeColor + ',' + borderAlpha + ')';
|
|
}
|
|
ctx.fillStyle = 'rgba(' + fillColor + ',' + fillAlpha + ')';
|
|
ctx.lineWidth = this.handle.lineWidth;
|
|
ctx.strokeStyle = strokeStyle;
|
|
|
|
// Start to draw it.
|
|
// Can't use functions like rect or these individual parts
|
|
// would show its borders.
|
|
ctx.beginPath();
|
|
|
|
ctx.arc(center.x, center.y,
|
|
radius, endAngle, startAngle, counterclock);
|
|
ctx.lineTo(center.x, center.y - radius);
|
|
ctx.lineTo(center.x + (offset - center.x), center.y - radius);
|
|
ctx.arc(offset, center.y, radius, startAngle, endAngle, counterclock);
|
|
ctx.lineTo(center.x, center.y + radius);
|
|
|
|
// Note: When setting both the fill and stroke for a shape,
|
|
// make sure that you use fill() before stroke().
|
|
// Otherwise, the fill will overlap half of the stroke.
|
|
ctx.fill();
|
|
ctx.stroke();
|
|
ctx.closePath();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Return the mapping pixels according to the device pixel ratio.
|
|
* This may need to be put int the shared/js.
|
|
*
|
|
* @param {number} |px| original px distance.
|
|
* @return {number}
|
|
* @this {LockScreenSlide}
|
|
*/
|
|
LockScreenSlidePrototype._drawIconBG =
|
|
function lss_dibg() {
|
|
if (!this.useNewStyle) {
|
|
return;
|
|
}
|
|
|
|
var canvas = this.canvas;
|
|
var ctx = canvas.getContext('2d');
|
|
|
|
ctx.fillStyle = this.iconBG.left.color;
|
|
ctx.strokeStyle = 'transparent';
|
|
|
|
ctx.beginPath();
|
|
ctx.arc(this.track.from, this.track.y,
|
|
this.iconBG.radius, 0, 2 * Math.PI, false);
|
|
ctx.stroke();
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
ctx.fillStyle = this.iconBG.right.color;
|
|
ctx.beginPath();
|
|
ctx.arc(this.track.to, this.track.y,
|
|
this.iconBG.radius, 0, 2 * Math.PI, false);
|
|
ctx.fill();
|
|
ctx.stroke();
|
|
ctx.closePath();
|
|
};
|
|
|
|
/**
|
|
* Return the mapping pixels according to the device pixel ratio.
|
|
* This may need to be put int the shared/js.
|
|
*
|
|
* @param {number} |px| original px distance.
|
|
* @return {number}
|
|
* @this {LockScreenSlide}
|
|
*/
|
|
LockScreenSlidePrototype._dpx =
|
|
function lss_dpx(px) {
|
|
return px * window.devicePixelRatio;
|
|
};
|
|
|
|
/**
|
|
* Light the left and right icons when user touch on our LockScreen.
|
|
*
|
|
* @this {LockScreenSlide}
|
|
*/
|
|
LockScreenSlidePrototype._lightIcons =
|
|
function lss_lightIcons() {
|
|
this.areas.left.classList.remove('dark');
|
|
this.areas.right.classList.remove('dark');
|
|
};
|
|
|
|
/**
|
|
* Map and Y to canvas' X and Y.
|
|
* Note this should only be used when user want to draw something
|
|
* follow the user's input. If the canvans need adjust its position,
|
|
* the absolute coordinates should be used.
|
|
*
|
|
* @param {number} |x| absolution X.
|
|
* @param {number} |y| absolution Y.
|
|
* @return {[number]} Array of single pair of X and Y.
|
|
* @this {LockScreenSlide}
|
|
*/
|
|
LockScreenSlidePrototype._mapCoord =
|
|
function lss_mapCoord(x, y) {
|
|
var cw = this.canvas.clientWidth;
|
|
var ch = this.canvas.clientHeight;
|
|
|
|
return [cw * x / window.innerWidth,
|
|
ch * y / window.innerHeight];
|
|
};
|
|
|
|
/**
|
|
* Restore the arrow to the original position.
|
|
*
|
|
* @this {LockScreenSlide}
|
|
*/
|
|
LockScreenSlidePrototype._resetArrows =
|
|
function lss_restoreArrows() {
|
|
var canvas = this.canvas;
|
|
var ctx = canvas.getContext('2d');
|
|
ctx.drawImage(this.arrows.left,
|
|
this.arrows.ldraw.x,
|
|
this.arrows.ldraw.y,
|
|
this.arrows.left.width,
|
|
this.arrows.left.height);
|
|
ctx.drawImage(this.arrows.right,
|
|
this.arrows.rdraw.x,
|
|
this.arrows.rdraw.y,
|
|
this.arrows.right.width,
|
|
this.arrows.right.height);
|
|
};
|
|
|
|
/**
|
|
* Draw the handle with its initial states.
|
|
*
|
|
* @this {LockScreenSlide}
|
|
*/
|
|
LockScreenSlidePrototype._resetHandle =
|
|
function lss_resetHandle() {
|
|
this.states.slidingColorful = false;
|
|
this.states.slidingColorGradientEnd = false;
|
|
var centerx = this.center.x;
|
|
this._drawSlideTo(centerx);
|
|
};
|
|
|
|
/**
|
|
* Reset the states of touch after it's end.
|
|
*
|
|
* @this {LockScreenSlide}
|
|
*/
|
|
LockScreenSlidePrototype._resetTouchStates =
|
|
function lss_resetTouchStates() {
|
|
this.states.touch = {
|
|
id: null,
|
|
touched: false,
|
|
initX: this.center.x,
|
|
pageX: this.center.x,
|
|
pageY: this.center.y,
|
|
tx: 0,
|
|
prevX: this.center.x,
|
|
deltaX: 0
|
|
};
|
|
};
|
|
|
|
LockScreenSlidePrototype.publish =
|
|
function lss_publish(type, detail) {
|
|
window.dispatchEvent(new CustomEvent(type, {'detail': detail}));
|
|
};
|
|
|
|
LockScreenSlide.prototype = LockScreenSlidePrototype;
|
|
exports.LockScreenSlide = LockScreenSlide;
|
|
})(window);
|