Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
448 lines
14 KiB
JavaScript
Executable file
448 lines
14 KiB
JavaScript
Executable file
'use strict';
|
|
|
|
// test result indicator
|
|
var Status = {
|
|
NOT_FINISHED: 1,
|
|
FINISHED: 2,
|
|
ABORT: 3
|
|
};
|
|
|
|
// UTILS
|
|
var util = {
|
|
// Use this function to get correct click position on canvas
|
|
relMouseCoords: function(canvas, evt) {
|
|
var rect = canvas.getBoundingClientRect();
|
|
var eventX = evt.clientX - rect.left;
|
|
var eventY = evt.clientY - rect.top;
|
|
return {x: eventX, y: eventY};
|
|
},
|
|
|
|
clearScreen: function(ctx) {
|
|
ctx.fillStyle = 'white';
|
|
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
},
|
|
|
|
drawRectFromCoord: function(ctx, x1, y1, x2, y2) {
|
|
ctx.translate(0.5, 0.5);
|
|
ctx.strokeRect(x1, y1, x2 - x1, y2 - y1);
|
|
ctx.translate(-0.5, -0.5);
|
|
},
|
|
|
|
// Fill rect without overlapping border,
|
|
fillRectFromCoord: function(ctx, x1, y1, x2, y2) {
|
|
ctx.fillRect(x1 + 1, y1 + 1, (x2 - x1) - 1, (y2 - y1) - 1);
|
|
},
|
|
|
|
drawCircle: function(ctx, x, y, radius, width, color) {
|
|
ctx.lineWidth = width;
|
|
ctx.strokeStyle = color;
|
|
ctx.beginPath();
|
|
ctx.translate(0.5, 0.5);
|
|
ctx.arc(x, y, radius, 0, 2 * Math.PI);
|
|
ctx.translate(-0.5, -0.5);
|
|
ctx.closePath();
|
|
ctx.stroke();
|
|
},
|
|
|
|
distance_sqr: function(x1, y1, x2, y2) {
|
|
return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
|
|
},
|
|
|
|
// Return the index less or equal to num
|
|
binary_search: function(arr, num) {
|
|
var begin = 0;
|
|
var end = arr.length - 1;
|
|
while (begin < end) {
|
|
var pivot = Math.round((begin + end) / 2);
|
|
if (num < arr[pivot]) {
|
|
end = pivot - 1;
|
|
}
|
|
else if (num > arr[pivot]) {
|
|
begin = pivot + 1;
|
|
}
|
|
else {
|
|
return pivot;
|
|
}
|
|
}
|
|
if (num < arr[begin]) {
|
|
return begin - 1;
|
|
}
|
|
return begin;
|
|
}
|
|
};
|
|
|
|
// TEST 1
|
|
// Draw 4 circles on the corners and 1 circle in the center
|
|
var test1 = function(canvas) {
|
|
var ctx = canvas.getContext('2d');
|
|
var circle_radius = Math.min(canvas.width, canvas.height) * 0.1;
|
|
var circles_coord =
|
|
[[circle_radius, circle_radius],
|
|
[circle_radius, canvas.height - circle_radius],
|
|
[canvas.width - circle_radius, circle_radius],
|
|
[canvas.width - circle_radius, canvas.height - circle_radius],
|
|
[canvas.width / 2, canvas.height / 2]];
|
|
var lineWidth = 2;
|
|
var lineColor = 'red';
|
|
var lineColorClicked = 'lime';
|
|
var is_clicked = [];
|
|
|
|
var exitIconPositionX;
|
|
var exitIconPositionY;
|
|
var exitIconWidth;
|
|
var exitIconHeight;
|
|
|
|
(function init() {
|
|
util.clearScreen(ctx);
|
|
|
|
// Draw not-clicked circle
|
|
for (var i = 0; i < circles_coord.length; i++) {
|
|
util.drawCircle(ctx, circles_coord[i][0], circles_coord[i][1],
|
|
circle_radius, lineWidth, lineColor);
|
|
is_clicked[i] = false;
|
|
}
|
|
// Draw exit icon
|
|
var image = document.getElementById('icon-exit');
|
|
exitIconWidth = image.width;
|
|
exitIconHeight = image.height;
|
|
// Draw icon below center
|
|
exitIconPositionX = (canvas.width - exitIconWidth) / 2;
|
|
exitIconPositionY = canvas.height * 0.65;
|
|
ctx.drawImage(image, exitIconPositionX, exitIconPositionY);
|
|
})();
|
|
|
|
// Touch test, change circle color if touch point is in the circle.
|
|
// Test ends if all circles are touched
|
|
this.test = function(x, y) {
|
|
// Clicked on exit button
|
|
if (x > exitIconPositionX && x <= exitIconPositionX + exitIconWidth &&
|
|
y > exitIconPositionY && y <= exitIconPositionY + exitIconHeight) {
|
|
return Status.ABORT;
|
|
}
|
|
for (var i = 0; i < circles_coord.length; i++) {
|
|
if (!is_clicked[i] &&
|
|
util.distance_sqr(x, y, circles_coord[i][0], circles_coord[i][1]) <
|
|
circle_radius * circle_radius) {
|
|
// Clear red circle by covering a bigger white circle on it
|
|
util.drawCircle(ctx, circles_coord[i][0], circles_coord[i][1],
|
|
circle_radius, lineWidth + 2, 'white');
|
|
// Draw new circle
|
|
util.drawCircle(ctx, circles_coord[i][0], circles_coord[i][1],
|
|
circle_radius, lineWidth, lineColorClicked);
|
|
|
|
is_clicked[i] = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (var i = 0; i < circles_coord.length; i++) {
|
|
if (!is_clicked[i]) {
|
|
return Status.NOT_FINISHED;
|
|
}
|
|
}
|
|
// Test over
|
|
return Status.FINISHED;
|
|
};
|
|
};
|
|
|
|
// TEST 2
|
|
// Draw several cubes on the edge of screen
|
|
var test2 = function(canvas) {
|
|
var ctx = canvas.getContext('2d');
|
|
var aspect = canvas.width / canvas.height;
|
|
var cubes_num_vertical = 20;
|
|
var cubes_num_horizontal = Math.round(cubes_num_vertical * aspect);
|
|
var cube_width = canvas.width / cubes_num_horizontal;
|
|
var cube_height = canvas.height / cubes_num_vertical;
|
|
|
|
var cube_coord_x = [];
|
|
var cube_coord_y = [];
|
|
var is_clicked_top = [];
|
|
var is_clicked_bottom = [];
|
|
var is_clicked_left = [];
|
|
var is_clicked_right = [];
|
|
var total_clicked = 0;
|
|
|
|
var exitIconPositionX;
|
|
var exitIconPositionY;
|
|
var exitIconWidth;
|
|
var exitIconHeight;
|
|
|
|
(function init() {
|
|
util.clearScreen(ctx);
|
|
ctx.strokeStyle = 'black';
|
|
ctx.fillStyle = 'lime';
|
|
ctx.lineWidth = 1;
|
|
|
|
// Because width/height of screen can not be divided evenly
|
|
// So we precaculate the grid (coordination) of cubes for future use
|
|
for (var i = 0; i < cubes_num_horizontal; i++) {
|
|
cube_coord_x[i] = Math.round(cube_width * i);
|
|
}
|
|
cube_coord_x[cubes_num_horizontal] = canvas.width - 1;
|
|
for (var i = 0; i < cubes_num_vertical; i++) {
|
|
cube_coord_y[i] = Math.round(cube_height * i);
|
|
}
|
|
cube_coord_y[cubes_num_vertical] = canvas.height - 1;
|
|
|
|
// Draw Top/Bottom/Left/Right cubes
|
|
for (var i = 0; i < cubes_num_horizontal; i++) {
|
|
util.drawRectFromCoord(ctx, cube_coord_x[i], cube_coord_y[0],
|
|
cube_coord_x[i + 1], cube_coord_y[1]);
|
|
is_clicked_top[i] = false;
|
|
}
|
|
for (var i = 0; i < cubes_num_horizontal; i++) {
|
|
util.drawRectFromCoord(ctx,
|
|
cube_coord_x[i],
|
|
cube_coord_y[cubes_num_vertical - 1],
|
|
cube_coord_x[i + 1],
|
|
cube_coord_y[cubes_num_vertical]);
|
|
is_clicked_bottom[i] = false;
|
|
}
|
|
for (var i = 0; i < cubes_num_vertical; i++) {
|
|
util.drawRectFromCoord(ctx,
|
|
cube_coord_x[0],
|
|
cube_coord_y[i],
|
|
cube_coord_x[1],
|
|
cube_coord_y[i + 1]);
|
|
is_clicked_left[i] = false;
|
|
}
|
|
for (var i = 0; i < cubes_num_vertical; i++) {
|
|
util.drawRectFromCoord(ctx,
|
|
cube_coord_x[cubes_num_horizontal - 1],
|
|
cube_coord_y[i],
|
|
cube_coord_x[cubes_num_horizontal],
|
|
cube_coord_y[i + 1]);
|
|
is_clicked_right[i] = false;
|
|
}
|
|
|
|
// Draw exit icon
|
|
var image = document.getElementById('icon-exit');
|
|
exitIconWidth = image.width;
|
|
exitIconHeight = image.height;
|
|
exitIconPositionX = (canvas.width - exitIconWidth) / 2;
|
|
exitIconPositionY = (canvas.height - exitIconHeight) / 2;
|
|
ctx.drawImage(image, exitIconPositionX, exitIconPositionY);
|
|
}
|
|
)();
|
|
|
|
// Touch test, fill cubes if touch point is in the cube.
|
|
// Test ends if all cubes are touched
|
|
this.test = function(x, y) {
|
|
// Clicked on exit button
|
|
if (x > exitIconPositionX && x <= exitIconPositionX + exitIconWidth &&
|
|
y > exitIconPositionY && y <= exitIconPositionY + exitIconHeight) {
|
|
return Status.ABORT;
|
|
}
|
|
|
|
if (cube_coord_x[1] <= x && x <= cube_coord_x[cubes_num_horizontal - 1] &&
|
|
cube_coord_y[1] <= y && y <= cube_coord_y[cubes_num_vertical - 1]) {
|
|
return Status.NOT_FINISHED;
|
|
}
|
|
|
|
var startXi = util.binary_search(cube_coord_x, x);
|
|
var startYi = util.binary_search(cube_coord_y, y);
|
|
util.fillRectFromCoord(ctx,
|
|
cube_coord_x[startXi],
|
|
cube_coord_y[startYi],
|
|
cube_coord_x[startXi + 1],
|
|
cube_coord_y[startYi + 1]);
|
|
if (startYi == 0 && !is_clicked_top[startXi]) {
|
|
is_clicked_top[startXi] = true;
|
|
}
|
|
if (startYi == cubes_num_vertical - 1 && !is_clicked_bottom[startXi]) {
|
|
is_clicked_bottom[startXi] = true;
|
|
}
|
|
if (startXi == 0 && !is_clicked_left[startYi]) {
|
|
is_clicked_left[startYi] = true;
|
|
}
|
|
if (startXi == cubes_num_horizontal - 1 && !is_clicked_right[startYi]) {
|
|
is_clicked_right[startYi] = true;
|
|
}
|
|
|
|
for (var i = 0; i < cubes_num_vertical; i++) {
|
|
if (!is_clicked_left[i] || !is_clicked_right[i]) {
|
|
return Status.NOT_FINISHED;
|
|
}
|
|
}
|
|
for (var i = 0; i < cubes_num_horizontal; i++) {
|
|
if (!is_clicked_top[i] || !is_clicked_bottom[i]) {
|
|
return Status.NOT_FINISHED;
|
|
}
|
|
}
|
|
// Test over
|
|
return Status.FINISHED;
|
|
};
|
|
};
|
|
|
|
var test_driver = function() {
|
|
var current_test = null;
|
|
var canvas = document.getElementById('canvas');
|
|
canvas.width = window.innerWidth;
|
|
canvas.height = window.innerHeight;
|
|
canvas.style.width = canvas.width + 'px';
|
|
canvas.style.height = canvas.height + 'px';
|
|
|
|
// returns true if no other tests
|
|
function proceed() {
|
|
if (current_test == null) {
|
|
current_test = new test1(canvas);
|
|
return false;
|
|
}
|
|
else if (current_test instanceof test1) {
|
|
current_test = new test2(canvas);
|
|
return false;
|
|
}
|
|
else if (current_test instanceof test2) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
proceed();
|
|
|
|
// returns whether all tests are finished
|
|
this.touch = function(x, y) {
|
|
var touchResult = current_test.test(x, y);
|
|
if (touchResult == Status.ABORT) {
|
|
return Status.ABORT;
|
|
}
|
|
// one test finished, go on next test
|
|
if (touchResult == Status.FINISHED) {
|
|
var isAllFinished = proceed();
|
|
if (isAllFinished) {
|
|
return Status.FINISHED;
|
|
}
|
|
}
|
|
return Status.NOT_FINISHED;
|
|
};
|
|
};
|
|
|
|
// Handle touch input, Codes modified from
|
|
// https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Events/Touch_events
|
|
var touchTest = function() {
|
|
var ongoingTouches = [];
|
|
var driver = null;
|
|
var canvas = null;
|
|
|
|
function copyTouch(touch) {
|
|
return { identifier: touch.identifier,
|
|
pageX: touch.pageX,
|
|
pageY: touch.pageY };
|
|
}
|
|
|
|
function ongoingTouchIndexById(idToFind) {
|
|
for (var i = 0; i < ongoingTouches.length; i++) {
|
|
var id = ongoingTouches[i].identifier;
|
|
|
|
if (id == idToFind) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1; // not found
|
|
}
|
|
|
|
function handleEvent(evt) {
|
|
var touches = evt.changedTouches;
|
|
var result;
|
|
evt.preventDefault();
|
|
|
|
switch (evt.type) {
|
|
case 'touchstart':
|
|
for (var i = 0; i < touches.length; i++) {
|
|
ongoingTouches.push(copyTouch(touches[i]));
|
|
var coord = util.relMouseCoords(canvas, touches[i]);
|
|
result = driver.touch(coord.x, coord.y);
|
|
}
|
|
break;
|
|
case 'touchmove':
|
|
for (var i = 0; i < touches.length; i++) {
|
|
var coord = util.relMouseCoords(canvas, touches[i]);
|
|
var idx = ongoingTouchIndexById(touches[i].identifier);
|
|
|
|
if (idx >= 0) {
|
|
var coord_last = util.relMouseCoords(canvas, ongoingTouches[idx]);
|
|
result = driver.touch(coord.x, coord.y);
|
|
// swap in the new touch record
|
|
ongoingTouches.splice(idx, 1, copyTouch(touches[i]));
|
|
} else {
|
|
console.log('can\'t figure out which touch to continue');
|
|
}
|
|
}
|
|
break;
|
|
case 'touchend':
|
|
for (var i = 0; i < touches.length; i++) {
|
|
var coord = util.relMouseCoords(canvas, touches[i]);
|
|
var idx = ongoingTouchIndexById(touches[i].identifier);
|
|
|
|
if (idx >= 0) {
|
|
ongoingTouches.splice(i, 1); // remove it; we're done
|
|
} else {
|
|
console.log('can\'t figure out which touch to end');
|
|
}
|
|
}
|
|
break;
|
|
case 'touchcancel':
|
|
for (var i = 0; i < touches.length; i++) {
|
|
ongoingTouches.splice(i, 1); // remove it; we're done
|
|
}
|
|
break;
|
|
}
|
|
|
|
// All tests over
|
|
if (result == Status.FINISHED || result == Status.ABORT) {
|
|
// Clear remaining touches
|
|
touches = null;
|
|
canvas.removeEventListener('touchstart', handleEvent, false);
|
|
canvas.removeEventListener('touchend', handleEvent, false);
|
|
canvas.removeEventListener('touchcancel', handleEvent, false);
|
|
canvas.removeEventListener('touchleave', handleEvent, false);
|
|
canvas.removeEventListener('touchmove', handleEvent, false);
|
|
console.log('uninitialized.');
|
|
|
|
if (result == Status.ABORT) {
|
|
alert('Test terminated by user');
|
|
} else {
|
|
alert('Test Success!');
|
|
}
|
|
document.mozCancelFullScreen();
|
|
// This is a hack to close touch test, uitest.js will close this iframe
|
|
window.parent.window.location.hash = '';
|
|
}
|
|
}
|
|
|
|
this.init = function() {
|
|
driver = new test_driver();
|
|
|
|
if (canvas == null) {
|
|
canvas = document.getElementsByTagName('canvas')[0];
|
|
canvas.addEventListener('touchstart', handleEvent, false);
|
|
canvas.addEventListener('touchend', handleEvent, false);
|
|
canvas.addEventListener('touchcancel', handleEvent, false);
|
|
canvas.addEventListener('touchleave', handleEvent, false);
|
|
canvas.addEventListener('touchmove', handleEvent, false);
|
|
console.log('initialized.');
|
|
}
|
|
};
|
|
};
|
|
|
|
// XXX: There is a problem of getting into fullscreen
|
|
// When we called mozRequestFullscreen, it actually resized two times.
|
|
// We get almost fullscreen (except status bar) after first event, and truely
|
|
// fullscreen after second event. So we need to wait for the second event.
|
|
var resizedTimes = 0;
|
|
window.addEventListener('resize', function startTest() {
|
|
if (resizedTimes < 1) {
|
|
resizedTimes++;
|
|
}
|
|
else {
|
|
window.removeEventListener('resize', startTest);
|
|
var test = new touchTest;
|
|
test.init();
|
|
}
|
|
});
|
|
|
|
window.addEventListener('load', function() {
|
|
document.getElementById('canvas').classList.remove('invisible');
|
|
var exitButton = document.getElementById('fullscreen-exit');
|
|
document.body.mozRequestFullScreen();
|
|
});
|