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 = { const DEFAULTS = {
board: Tessellate.BOARD_STYLES.HEX, board: Tessellate.BOARD_STYLES.HEX,
style: Tessellate.DRAW_STYLES.FILL, style: Tessellate.DRAW_STYLES.FILL,
@@ -5,11 +8,26 @@ const DEFAULTS = {
tile: Tessellate.TILE_STYLES.HEX, 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 { class Demo {
constructor() { constructor() {
[ [
'setOriginTile', 'setOriginTile',
'onTap', 'tap',
'pressStart',
'press',
'createTile', 'createTile',
'drawTile', 'drawTile',
'draw', 'draw',
@@ -21,11 +39,14 @@ class Demo {
this.map = {}; this.map = {};
this.taps = []; this.taps = [];
this.ripples = [];
this.setOriginTile(); this.setOriginTile();
this.tessellate = new Tessellate(Object.assign({ this.tessellate = new Tessellate(Object.assign({
element: '#container', element: '#container',
tap: this.onTap, tap: this.tap,
pressStart: this.pressStart,
press: this.press,
draw: this.draw, draw: this.draw,
}, this.settings)); }, this.settings));
} }
@@ -62,7 +83,7 @@ class Demo {
}); });
} }
onTap(tap) { tap(tap) {
const {x, y, z} = tap.tile.getPoint(); const {x, y, z} = tap.tile.getPoint();
console.log(x, y, z); console.log(x, y, z);
@@ -70,40 +91,77 @@ class Demo {
this.map[key].pips = Tessellate.utils.random(1,7); this.map[key].pips = Tessellate.utils.random(1,7);
console.log(this.map[key].pips); 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({ return new Tessellate.Cell({
x, y, x, y,
scale: Tessellate.utils.random(7, 9) / 10, scale,
drawStyle, drawStyle,
tileStyle, tileStyle,
orientation, orientation,
red: Tessellate.utils.random(255), red,
green: Tessellate.utils.random(255), green,
blue: Tessellate.utils.random(255), blue,
alpha: Tessellate.utils.random(25,75)/100 alpha,
}); });
} }
drawTile({x, y, z}, context, scale) { drawTile({x, y, z}, context, scale) {
const key = `${ x },${ z != null ? z : y }`; 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 tile = this.map[key];
const pixelPoint = this.tessellate.tileToPixel(x, y, z); const pixelPoint = this.tessellate.tileToPixel(x, y, z);
@@ -115,6 +173,21 @@ class Demo {
draw({context, scale, tilePoints}) { draw({context, scale, tilePoints}) {
tilePoints.forEach(tilePoint => this.drawTile(tilePoint, context, scale)); 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 => { this.taps.forEach(cell => {
const pixelPoint = this.tessellate.tileToPixel(cell.x, cell.y); const pixelPoint = this.tessellate.tileToPixel(cell.x, cell.y);
Tessellate.TILES[cell.tileStyle][cell.drawStyle](context, scale, pixelPoint.getX(), pixelPoint.getY(), cell); Tessellate.TILES[cell.tileStyle][cell.drawStyle](context, scale, pixelPoint.getX(), pixelPoint.getY(), cell);

View File

@@ -2,7 +2,24 @@
import {noop} from './utils.js'; import {noop} from './utils.js';
const DEFAULTS = { const DEFAULTS = {
debug: false,
element: document.body, 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, wheelFactor: -100,
}; };
@@ -10,33 +27,15 @@ export default class OnTap {
constructor(settings) { constructor(settings) {
this.settings = Object.assign({}, DEFAULTS, settings); this.settings = Object.assign({}, DEFAULTS, settings);
this.debug = false;
this.state = { this.state = {
clickStartTime: null tapStartTime: 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
},
}; };
[ [
// TODO: don't set up listeners for these two
'tapStart',
'pressStart',
'mousedown', 'mousedown',
'mouseup', 'mouseup',
'mousemove', 'mousemove',
@@ -47,26 +46,40 @@ export default class OnTap {
'touchcancel', 'touchcancel',
'wheel', 'wheel',
].map(method => { ].forEach(method => {
this[method] = this[method].bind(this); this[method] = this[method].bind(this);
this.settings.element.addEventListener(method, this[method]); this.settings.element.addEventListener(method, this[method]);
}); });
} }
mousedown(event) { tapStart(event, mobile = false) {
if (this.debug) console.debug('onTap.mousedown', event); 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.lastX = event.offsetX;
this.state.lastY = event.offsetY; this.state.lastY = event.offsetY;
this.state.clickStartTime = event.timeStamp;
} this.tapStart(event, false);
} }
touchstart(event) { touchstart(event) {
event.preventDefault(); event.preventDefault();
if (this.debug) console.debug('onTap.touchstart', event); if (this.settings.debug) console.debug('onTap.touchstart', event);
const touches = [...event.touches]; const touches = [...event.touches];
event.offsetX = touches.reduce((memo, touch) => memo + touch.pageX, 0) / touches.length; 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) { if (event.touches.length > 1) {
this.state.pinching = true; this.state.pinching = true;
clearTimeout(this.state.pressTO);
} }
if (!this.state.clickStartTime) { this.tapStart(event, true);
this.state.clickStartTime = event.timeStamp;
} }
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) { mouseup(event) {
if (this.debug) console.debug('onTap.mouseup', event); if (this.settings.debug) console.debug('onTap.mouseup', event);
if (!this.state.moving) { 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.moving = null;
this.state.lastX = null; this.state.lastX = null;
this.state.lastY = null; this.state.lastY = null;
this.state.clickStartTime = null; this.state.tapStartTime = null;
clearTimeout(this.state.pressTO);
} }
touchend(event) { touchend(event) {
event.preventDefault(); event.preventDefault();
if (this.debug) console.debug('onTap.touchend', event); if (this.settings.debug) console.debug('onTap.touchend', event);
const touches = [...event.touches]; const touches = [...event.touches];
@@ -117,7 +144,15 @@ export default class OnTap {
} }
if (!(this.state.moving || this.state.pinching)) { 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) { if (event.touches.length <= 1) {
@@ -129,25 +164,26 @@ export default class OnTap {
this.state.moving = null; this.state.moving = null;
this.state.lastX = null; this.state.lastX = null;
this.state.lastY = null; this.state.lastY = null;
this.state.clickStartTime = null; this.state.tapStartTime = null;
} }
} }
mousemove(event) { 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 (!this.state.moving) {
if ((Math.abs(event.offsetX - this.state.lastX) > this.actions.move.threshold) if ((Math.abs(event.offsetX - this.state.lastX) > this.settings.moveThreshold)
|| (Math.abs(event.offsetY - this.state.lastY) > this.actions.move.threshold)) { || (Math.abs(event.offsetY - this.state.lastY) > this.settings.moveThreshold)) {
this.state.moving = true; this.state.moving = true;
clearTimeout(this.state.pressTO);
} }
} }
if (this.state.moving) { if (this.state.moving) {
event.deltaX = event.offsetX - this.state.lastX, event.deltaX = event.offsetX - this.state.lastX,
event.deltaY = event.offsetY - this.state.lastY event.deltaY = event.offsetY - this.state.lastY
this.actions.move.callback(event); this.settings.move(event);
this.state.lastX = event.offsetX; this.state.lastX = event.offsetX;
this.state.lastY = event.offsetY; this.state.lastY = event.offsetY;
@@ -158,9 +194,9 @@ export default class OnTap {
touchmove(event) { touchmove(event) {
event.preventDefault(); 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]; const touches = [...event.touches];
event.offsetX = touches.reduce((memo, touch) => memo + touch.pageX, 0) / touches.length; event.offsetX = touches.reduce((memo, touch) => memo + touch.pageX, 0) / touches.length;
event.offsetY = touches.reduce((memo, touch) => memo + touch.pageY, 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) { if (this.state.pinching) {
event.scaleStep = event.scale / (this.state.lastPinch || 1); event.scaleStep = event.scale / (this.state.lastPinch || 1);
this.actions.zoom.callback(event); this.settings.zoom(event);
this.state.lastPinch = event.scale; this.state.lastPinch = event.scale;
} }
if (!this.state.moving) { if (!this.state.moving) {
if ((Math.abs(event.offsetX - this.state.lastX) > this.actions.move.threshold) if ((Math.abs(event.offsetX - this.state.lastX) > this.settings.moveThreshold)
|| (Math.abs(event.offsetY - this.state.lastY) > this.actions.move.threshold)) { || (Math.abs(event.offsetY - this.state.lastY) > this.settings.moveThreshold)) {
this.state.moving = true; this.state.moving = true;
clearTimeout(this.state.pressTO);
} }
} }
@@ -184,7 +221,7 @@ export default class OnTap {
event.deltaX = event.offsetX - this.state.lastX, event.deltaX = event.offsetX - this.state.lastX,
event.deltaY = event.offsetY - this.state.lastY event.deltaY = event.offsetY - this.state.lastY
this.actions.move.callback(event); this.settings.move(event);
this.state.lastX = event.offsetX; this.state.lastX = event.offsetX;
this.state.lastY = event.offsetY; this.state.lastY = event.offsetY;
@@ -197,11 +234,11 @@ export default class OnTap {
} }
wheel(event) { 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); 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 = { const DEFAULTS = {
tile: HEX, tile: HEX,
board: HEX, board: HEX,
tap: utils.noop,
draw: utils.noop,
orientation: FLAT, orientation: FLAT,
negativeTiles: true, negativeTiles: true,
tap: utils.noop,
pressStart: utils.noop,
press: utils.noop,
draw: utils.noop,
}; };
function selectCartographer(board, orientation) { function selectCartographer(board, orientation) {
@@ -72,6 +76,9 @@ export class Tessellate {
[ [
'checkSettings', 'checkSettings',
'tap', 'tap',
'doubletap',
'pressStart',
'press',
'move', 'move',
'zoom', 'zoom',
'pixelToTile', 'pixelToTile',
@@ -87,14 +94,15 @@ export class Tessellate {
draw: this.draw draw: this.draw
}); });
this.onTap = new OnTap({ this.onTap = new OnTap(Object.assign({
element: this.settings.element, element: this.settings.element,
tap: this.tap, tap: this.tap,
doubletap: this.doubletap, doubletap: this.doubletap,
hold: this.hold, pressStart: this.pressStart,
press: this.press,
move: this.move, move: this.move,
zoom: this.zoom, zoom: this.zoom,
}); }, funky.pick(this.settings, ['desktopPress', 'moveThreshold', 'doubletapThreshold', 'pressThreshold', 'wheelFactor'])));
const cartographer = selectCartographer(this.settings.board, this.settings.orientation); const cartographer = selectCartographer(this.settings.board, this.settings.orientation);
this.cartographer = new cartographer(Object.assign({ this.cartographer = new cartographer(Object.assign({
@@ -134,6 +142,7 @@ export class Tessellate {
} }
doubletap(event) { doubletap(event) {
console.log('DOUBLETAP', event);
let point = new Point(event.offsetX, event.offsetY); let point = new Point(event.offsetX, event.offsetY);
let tile = this.cartographer.pixelToTile(point); let tile = this.cartographer.pixelToTile(point);
@@ -143,11 +152,10 @@ export class Tessellate {
tile tile
}; };
console.log('DOUBLETAP');
console.log(tap); console.log(tap);
} }
hold(event) { pressStart(event) {
let point = new Point(event.offsetX, event.offsetY); let point = new Point(event.offsetX, event.offsetY);
let tile = this.cartographer.pixelToTile(point); let tile = this.cartographer.pixelToTile(point);
@@ -157,8 +165,20 @@ export class Tessellate {
tile tile
}; };
console.log('HOLD'); this.settings.pressStart(tap);
console.log(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) { move(event) {