VulpesOS/assets/webapps/system.gaiamobile.org/shared/js/lockscreen_slide.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

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);