A Pressing Matter (#11)

This commit is contained in:
gavin
2018-08-04 01:10:19 +00:00
committed by Gitea
parent 4b498338b1
commit d147a520bb
3 changed files with 213 additions and 83 deletions

View File

@@ -1,3 +1,6 @@
const ONE_SECOND = 1000;
const PRESS_RIPPLE = ONE_SECOND / 3;
const DEFAULTS = {
board: Tessellate.BOARD_STYLES.HEX,
style: Tessellate.DRAW_STYLES.FILL,
@@ -5,11 +8,26 @@ const DEFAULTS = {
tile: Tessellate.TILE_STYLES.HEX,
};
const pressRipple = function() {
const sinStart = 2 * Math.PI;
const halfPi = Math.PI / 2;
return pressFactor => Math.sin(sinStart + (pressFactor * halfPi)) + 1;
}();
const pressFade = function() {
const halfPi = Math.PI / 2;
return pressFactor => Math.sin(Math.PI + (pressFactor * halfPi)) + 1;
}();
class Demo {
constructor() {
[
'setOriginTile',
'onTap',
'tap',
'pressStart',
'press',
'createTile',
'drawTile',
'draw',
@@ -21,11 +39,14 @@ class Demo {
this.map = {};
this.taps = [];
this.ripples = [];
this.setOriginTile();
this.tessellate = new Tessellate(Object.assign({
element: '#container',
tap: this.onTap,
tap: this.tap,
pressStart: this.pressStart,
press: this.press,
draw: this.draw,
}, this.settings));
}
@@ -62,7 +83,7 @@ class Demo {
});
}
onTap(tap) {
tap(tap) {
const {x, y, z} = tap.tile.getPoint();
console.log(x, y, z);
@@ -70,40 +91,77 @@ class Demo {
this.map[key].pips = Tessellate.utils.random(1,7);
console.log(this.map[key].pips);
// console.log(tap.tile.getPoint());
//
// this.taps.push(this.createTile(
// tap.tile.x,
// tap.tile.y,
//
// Tessellate.utils.random(Tessellate.DRAW_STYLES),
// Tessellate.utils.random(Tessellate.TILE_STYLES),
// Tessellate.utils.random(Tessellate.TILE_ORIENTATIONS),
// ));
}
createTile(x, y, drawStyle, tileStyle, orientation) {
pressStart(tap) {
this.ripples.push({
timestamp: tap.event.timeStamp,
cell: this.createTile({
x: tap.tile.x,
y: tap.tile.y,
scale: 1.0,
drawStyle: Tessellate.DRAW_STYLES.FILL,
tileStyle: Tessellate.TILE_STYLES.CIRCLE,
orientation: Tessellate.TILE_ORIENTATIONS.FLAT,
red: 255,
green: 127,
blue: 127,
alpha: 0.5,
})
});
this.taps.push(this.createTile({
x: tap.tile.x,
y: tap.tile.y,
drawStyle: Tessellate.utils.random(Tessellate.DRAW_STYLES),
tileStyle: Tessellate.utils.random(Tessellate.TILE_STYLES),
orientation: Tessellate.utils.random(Tessellate.TILE_ORIENTATIONS),
}));
}
press(tap) {
console.log('PRESS END');
}
createTile({x, y,
drawStyle, tileStyle, orientation,
scale = Tessellate.utils.random(7, 9) / 10,
red = Tessellate.utils.random(255),
green = Tessellate.utils.random(255),
blue = Tessellate.utils.random(255),
alpha = Tessellate.utils.random(25, 75) / 100,
}) {
return new Tessellate.Cell({
x, y,
scale: Tessellate.utils.random(7, 9) / 10,
scale,
drawStyle,
tileStyle,
orientation,
red: Tessellate.utils.random(255),
green: Tessellate.utils.random(255),
blue: Tessellate.utils.random(255),
alpha: Tessellate.utils.random(25,75)/100
red,
green,
blue,
alpha,
});
}
drawTile({x, y, z}, context, scale) {
const key = `${ x },${ z != null ? z : y }`;
this.map[key] = this.map[key] || this.createTile(x, y, this.settings.style, this.settings.tile, this.settings.orientation);
this.map[key] = this.map[key] || this.createTile({
x, y,
drawStyle: this.settings.style,
tileStyle: this.settings.tile,
orientation: this.settings.orientation,
});
const tile = this.map[key];
const pixelPoint = this.tessellate.tileToPixel(x, y, z);
@@ -115,6 +173,21 @@ class Demo {
draw({context, scale, tilePoints}) {
tilePoints.forEach(tilePoint => this.drawTile(tilePoint, context, scale));
const now = Date.now();
this.ripples.forEach(({timestamp, cell}) => {
let pressFactor = (now - timestamp) / PRESS_RIPPLE;
pressFactor = pressFactor > 1 ? 1 : pressFactor;
Object.assign(cell, {
scale: pressRipple(pressFactor),
alpha: pressFade(pressFactor),
});
const pixelPoint = this.tessellate.tileToPixel(cell.x, cell.y);
Tessellate.TILES[cell.tileStyle][cell.drawStyle](context, scale, pixelPoint.getX(), pixelPoint.getY(), cell);
});
this.ripples = this.ripples.filter(ripple => (ripple.timestamp + PRESS_RIPPLE) > now);
this.taps.forEach(cell => {
const pixelPoint = this.tessellate.tileToPixel(cell.x, cell.y);
Tessellate.TILES[cell.tileStyle][cell.drawStyle](context, scale, pixelPoint.getX(), pixelPoint.getY(), cell);

View File

@@ -2,7 +2,24 @@
import {noop} from './utils.js';
const DEFAULTS = {
debug: false,
element: document.body,
desktopPress: false,
tap: noop,
tapStart: noop,
move: noop,
doubletap: noop,
press: noop,
pressStart: noop,
zoom: noop,
moveThreshold: 5,
doubletapThreshold: 500,
pressThreshold: 1000,
wheelFactor: -100,
};
@@ -10,33 +27,15 @@ export default class OnTap {
constructor(settings) {
this.settings = Object.assign({}, DEFAULTS, settings);
this.debug = false;
this.state = {
clickStartTime: null
};
this.actions = {
tap: {
callback: settings.tap || noop
},
move: {
threshold: settings.moveThreshold || 5, // greater than in pixels
callback: settings.move || noop
},
doubletap: {
threshold: settings.doubletapThreshold || 500, // less than in milliseconds
callback: settings.doubletap || noop
},
press: {
threshold: settings.pressThreshold || 1000, // greater than or equal to in milliseconds
callback: settings.press || noop
},
zoom: {
callback: settings.zoom || noop
},
tapStartTime: null,
};
[
// TODO: don't set up listeners for these two
'tapStart',
'pressStart',
'mousedown',
'mouseup',
'mousemove',
@@ -47,26 +46,40 @@ export default class OnTap {
'touchcancel',
'wheel',
].map(method => {
].forEach(method => {
this[method] = this[method].bind(this);
this.settings.element.addEventListener(method, this[method]);
});
}
mousedown(event) {
if (this.debug) console.debug('onTap.mousedown', event);
tapStart(event, mobile = false) {
if (this.settings.debug) console.debug('onTap.tapStart', event);
if (!this.state.tapStartTime) {
this.state.tapStartTime = event.timeStamp;
if (mobile || this.settings.desktopPress) {
clearTimeout(this.state.pressTO);
this.state.pressTO = setTimeout(this.pressStart, this.settings.pressThreshold);
}
this.settings.tapStart(event);
}
}
mousedown(event) {
if (this.settings.debug) console.debug('onTap.mousedown', event);
if (!this.state.clickStartTime) {
this.state.lastX = event.offsetX;
this.state.lastY = event.offsetY;
this.state.clickStartTime = event.timeStamp;
}
this.tapStart(event, false);
}
touchstart(event) {
event.preventDefault();
if (this.debug) console.debug('onTap.touchstart', event);
if (this.settings.debug) console.debug('onTap.touchstart', event);
const touches = [...event.touches];
event.offsetX = touches.reduce((memo, touch) => memo + touch.pageX, 0) / touches.length;
@@ -77,30 +90,44 @@ export default class OnTap {
if (event.touches.length > 1) {
this.state.pinching = true;
clearTimeout(this.state.pressTO);
}
if (!this.state.clickStartTime) {
this.state.clickStartTime = event.timeStamp;
this.tapStart(event, true);
}
pressStart(event = {timeStamp: Date.now(), offsetX: this.state.lastX, offsetY: this.state.lastY}) {
if (this.settings.debug) console.debug('onTap.pressStart', event);
this.settings.pressStart(event);
}
mouseup(event) {
if (this.debug) console.debug('onTap.mouseup', event);
if (this.settings.debug) console.debug('onTap.mouseup', event);
if (!this.state.moving) {
this.actions.tap.callback(event);
event.duration = event.timeStamp - this.state.tapStartTime;
if (this.settings.desktopPress && event.duration >= this.settings.pressThreshold) {
this.settings.press(event);
}
else {
clearTimeout(this.state.pressTO);
this.settings.tap(event);
}
}
this.state.moving = null;
this.state.lastX = null;
this.state.lastY = null;
this.state.clickStartTime = null;
this.state.tapStartTime = null;
clearTimeout(this.state.pressTO);
}
touchend(event) {
event.preventDefault();
if (this.debug) console.debug('onTap.touchend', event);
if (this.settings.debug) console.debug('onTap.touchend', event);
const touches = [...event.touches];
@@ -117,7 +144,15 @@ export default class OnTap {
}
if (!(this.state.moving || this.state.pinching)) {
this.actions.tap.callback(event);
event.duration = event.timeStamp - this.state.tapStartTime;
if (event.duration >= this.settings.pressThreshold) {
this.settings.press(event);
}
else {
clearTimeout(this.state.pressTO);
this.settings.tap(event);
}
}
if (event.touches.length <= 1) {
@@ -129,25 +164,26 @@ export default class OnTap {
this.state.moving = null;
this.state.lastX = null;
this.state.lastY = null;
this.state.clickStartTime = null;
this.state.tapStartTime = null;
}
}
mousemove(event) {
if (this.debug) console.debug('onTap.mousemove', event);
if (this.settings.debug) console.debug('onTap.mousemove', event);
if (this.state.clickStartTime) {
if (this.state.tapStartTime) {
if (!this.state.moving) {
if ((Math.abs(event.offsetX - this.state.lastX) > this.actions.move.threshold)
|| (Math.abs(event.offsetY - this.state.lastY) > this.actions.move.threshold)) {
if ((Math.abs(event.offsetX - this.state.lastX) > this.settings.moveThreshold)
|| (Math.abs(event.offsetY - this.state.lastY) > this.settings.moveThreshold)) {
this.state.moving = true;
clearTimeout(this.state.pressTO);
}
}
if (this.state.moving) {
event.deltaX = event.offsetX - this.state.lastX,
event.deltaY = event.offsetY - this.state.lastY
this.actions.move.callback(event);
this.settings.move(event);
this.state.lastX = event.offsetX;
this.state.lastY = event.offsetY;
@@ -158,9 +194,9 @@ export default class OnTap {
touchmove(event) {
event.preventDefault();
if (this.debug) console.debug('onTap.touchmove', event);
if (this.settings.debug) console.debug('onTap.touchmove', event);
if (this.state.clickStartTime) {
if (this.state.tapStartTime) {
const touches = [...event.touches];
event.offsetX = touches.reduce((memo, touch) => memo + touch.pageX, 0) / touches.length;
event.offsetY = touches.reduce((memo, touch) => memo + touch.pageY, 0) / touches.length;
@@ -168,15 +204,16 @@ export default class OnTap {
if (this.state.pinching) {
event.scaleStep = event.scale / (this.state.lastPinch || 1);
this.actions.zoom.callback(event);
this.settings.zoom(event);
this.state.lastPinch = event.scale;
}
if (!this.state.moving) {
if ((Math.abs(event.offsetX - this.state.lastX) > this.actions.move.threshold)
|| (Math.abs(event.offsetY - this.state.lastY) > this.actions.move.threshold)) {
if ((Math.abs(event.offsetX - this.state.lastX) > this.settings.moveThreshold)
|| (Math.abs(event.offsetY - this.state.lastY) > this.settings.moveThreshold)) {
this.state.moving = true;
clearTimeout(this.state.pressTO);
}
}
@@ -184,7 +221,7 @@ export default class OnTap {
event.deltaX = event.offsetX - this.state.lastX,
event.deltaY = event.offsetY - this.state.lastY
this.actions.move.callback(event);
this.settings.move(event);
this.state.lastX = event.offsetX;
this.state.lastY = event.offsetY;
@@ -197,11 +234,11 @@ export default class OnTap {
}
wheel(event) {
if (this.debug) console.debug('onTap.wheel', event);
if (this.settings.debug) console.debug('onTap.wheel', event);
event.scaleStep = 1 + (event.deltaY / this.settings.wheelFactor);
this.actions.zoom.callback(event);
this.settings.zoom(event);
}
}

View File

@@ -35,10 +35,14 @@ const TILES = {
const DEFAULTS = {
tile: HEX,
board: HEX,
tap: utils.noop,
draw: utils.noop,
orientation: FLAT,
negativeTiles: true,
tap: utils.noop,
pressStart: utils.noop,
press: utils.noop,
draw: utils.noop,
};
function selectCartographer(board, orientation) {
@@ -72,6 +76,9 @@ export class Tessellate {
[
'checkSettings',
'tap',
'doubletap',
'pressStart',
'press',
'move',
'zoom',
'pixelToTile',
@@ -87,14 +94,15 @@ export class Tessellate {
draw: this.draw
});
this.onTap = new OnTap({
this.onTap = new OnTap(Object.assign({
element: this.settings.element,
tap: this.tap,
doubletap: this.doubletap,
hold: this.hold,
pressStart: this.pressStart,
press: this.press,
move: this.move,
zoom: this.zoom,
});
}, funky.pick(this.settings, ['desktopPress', 'moveThreshold', 'doubletapThreshold', 'pressThreshold', 'wheelFactor'])));
const cartographer = selectCartographer(this.settings.board, this.settings.orientation);
this.cartographer = new cartographer(Object.assign({
@@ -134,6 +142,7 @@ export class Tessellate {
}
doubletap(event) {
console.log('DOUBLETAP', event);
let point = new Point(event.offsetX, event.offsetY);
let tile = this.cartographer.pixelToTile(point);
@@ -143,11 +152,10 @@ export class Tessellate {
tile
};
console.log('DOUBLETAP');
console.log(tap);
}
hold(event) {
pressStart(event) {
let point = new Point(event.offsetX, event.offsetY);
let tile = this.cartographer.pixelToTile(point);
@@ -157,8 +165,20 @@ export class Tessellate {
tile
};
console.log('HOLD');
console.log(tap);
this.settings.pressStart(tap);
}
press(event) {
let point = new Point(event.offsetX, event.offsetY);
let tile = this.cartographer.pixelToTile(point);
let tap = {
event,
point,
tile
};
this.settings.press(tap);
}
move(event) {