tapspace
Version:
A zoomable user interface lib for web apps
105 lines (96 loc) • 4.15 kB
JavaScript
module.exports = (viewport) => {
// Return: function, the wheel event handler.
//
return (ev) => {
// The scaling factor.
const factor = 1 - ev.deltaY / 1000
// The scaling pivot stays fixed.
// Use gesture center. Represented on the viewport.
const pivot = ev.center
// Note perspective.
// In perspective mode, the zooming in is implemented by moving closer.
// We cannot scale every distance equally in perspective projection.
// Doubling sizes close by requires much shorter move than doubling
// sizes far way. To double perceived size of an item, you must travel
// half the distance to the item.
// Attempt 1
// Let us try to set a constant target distance.
// At that distance, the scaling effect of the move should approximate
// the scaling apparent in the gesture.
//
// const camera = viewport.atCamera()
// const delta = camera.getVectorTo(pivot)
// const deltaUnit = delta.normalize()
// const trip = deltaUnit.scaleBy(ev.deltaY)
// viewport.translateBy(trip)
// Attempt 2
// Travel relative to the true event target.
// The zooming effect at the target approximates scaling the view.
// Note that if the pointer is on the background, the space itself is
// the target and the space is at viewport depth.
const camera = viewport.atCamera()
// We aim to move camera closer to the point of gesture target.
let selectedTarget // is a Component
let pivotOnTarget // is a Point.
// But what if target is the viewport itself? Or space?
// Optimally, target mass nearest the gesture mean line.
if (ev.target === viewport || ev.target === viewport.getHyperspace()) {
// const possibleTarget = viewport.findNearestMass()
// const possibleTarget = viewport.findNearestProjected(pivot)
const possibleTarget = viewport.navigationBasis
if (possibleTarget) {
selectedTarget = possibleTarget
pivotOnTarget = pivot.projectTo(possibleTarget, camera)
} else {
// Space is empty of solids. Thus use the viewport itself.
selectedTarget = viewport
pivotOnTarget = pivot
}
} else {
// Target is a component in space.
// We aim to move camera closer to the point of gesture target.
if (ev.target.isSolid()) {
selectedTarget = ev.target
pivotOnTarget = pivot.projectTo(ev.target, camera)
} else {
// Pick closest solid behind the non-solid.
// const possibleTarget = viewport.findNearestProjected(pivot)
const possibleTarget = viewport.navigationBasis
if (possibleTarget) {
selectedTarget = possibleTarget
pivotOnTarget = pivot.projectTo(possibleTarget, camera)
} else {
// Space is empty of solids, thus use the viewport itself.
selectedTarget = viewport
pivotOnTarget = pivot
}
}
}
// DEBUG
// const scale = selectedTarget.getScale().transitRaw(viewport)
// console.log('scale on viewport', scale)
// console.log('measured dilation', viewport.measureDilation(selectedTarget))
// console.log('selectedTarget', selectedTarget)
// We want to scale the length of the vector from camera to target.
const delta = camera.getVectorTo(pivotOnTarget)
// The desired difference vector.
const desired = delta.scaleBy(factor)
// A trip the viewport must take in order to reach the desired diff.
// delta + X = desired <=> X = desired - delta
const trip = desired.difference(delta)
// Travel
viewport.translateBy(trip)
// DEBUG
// console.log('ev.deltaY:', ev.deltaY)
// console.log('factor:', factor)
// console.log('camera rel.to viewport:', camera)
// console.log('gesture center on target:', pivotOnTarget)
// console.log('from camera to gesture center:', delta)
// console.log('desired vec from cam to center:', desired)
// console.log('trip for camera to take:', trip)
// Upgrade event
ev.navigationBasis = selectedTarget
// TODO limit extreme travel
viewport.emit('wheel', ev)
}
}