Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
1233 lines
37 KiB
JavaScript
Executable file
1233 lines
37 KiB
JavaScript
Executable file
'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);
|