Files
iD/modules/behavior/draw.js
2017-09-24 21:43:54 -04:00

248 lines
6.3 KiB
JavaScript

import { dispatch as d3_dispatch } from 'd3-dispatch';
import {
event as d3_event,
mouse as d3_mouse,
select as d3_select,
touches as d3_touches
} from 'd3-selection';
import { d3keybinding as d3_keybinding } from '../lib/d3.keybinding.js';
import { behaviorEdit } from './edit';
import { behaviorHover } from './hover';
import { behaviorTail } from './tail';
import {
geoChooseEdge,
geoEuclideanDistance
} from '../geo';
import { utilRebind } from '../util/rebind';
var usedTails = {};
var disableSpace = false;
var lastSpace = null;
export function behaviorDraw(context) {
var dispatch = d3_dispatch('move', 'click', 'clickWay',
'clickNode', 'undo', 'cancel', 'finish'),
keybinding = d3_keybinding('draw'),
hover = behaviorHover(context)
.altDisables(true)
.on('hover', context.ui().sidebar.hover),
tail = behaviorTail(),
edit = behaviorEdit(context),
closeTolerance = 4,
tolerance = 12,
mouseLeave = false,
lastMouse = null;
function datum() {
if (d3_event.altKey) return {};
if (d3_event.type === 'keydown') {
return (lastMouse && lastMouse.target.__data__) || {};
} else {
return d3_event.target.__data__ || {};
}
}
function mousedown() {
function point() {
var p = context.container().node();
return touchId !== null ? d3_touches(p).filter(function(p) {
return p.identifier === touchId;
})[0] : d3_mouse(p);
}
var element = d3_select(this),
touchId = d3_event.touches ? d3_event.changedTouches[0].identifier : null,
t1 = +new Date(),
p1 = point();
element.on('mousemove.draw', null);
d3_select(window).on('mouseup.draw', function() {
var t2 = +new Date(),
p2 = point(),
dist = geoEuclideanDistance(p1, p2);
element.on('mousemove.draw', mousemove);
d3_select(window).on('mouseup.draw', null);
if (dist < closeTolerance || (dist < tolerance && (t2 - t1) < 500)) {
// Prevent a quick second click
d3_select(window).on('click.draw-block', function() {
d3_event.stopPropagation();
}, true);
context.map().dblclickEnable(false);
window.setTimeout(function() {
context.map().dblclickEnable(true);
d3_select(window).on('click.draw-block', null);
}, 500);
click();
}
}, true);
}
function mousemove() {
lastMouse = d3_event;
dispatch.call('move', this, datum());
}
function mouseenter() {
mouseLeave = false;
}
function mouseleave() {
mouseLeave = true;
}
function click() {
var d = datum();
if (d.type === 'way') {
var dims = context.map().dimensions(),
mouse = context.mouse(),
pad = 5,
trySnap = mouse[0] > pad && mouse[0] < dims[0] - pad &&
mouse[1] > pad && mouse[1] < dims[1] - pad;
if (trySnap) {
var choice = geoChooseEdge(context.childNodes(d), context.mouse(), context.projection),
edge = [d.nodes[choice.index - 1], d.nodes[choice.index]];
dispatch.call('clickWay', this, choice.loc, edge);
} else {
dispatch.call('click', this, context.map().mouseCoordinates());
}
} else if (d.type === 'node') {
dispatch.call('clickNode', this, d);
} else {
dispatch.call('click', this, context.map().mouseCoordinates());
}
}
function space() {
d3_event.preventDefault();
d3_event.stopPropagation();
var currSpace = context.mouse();
if (disableSpace && lastSpace) {
var dist = geoEuclideanDistance(lastSpace, currSpace);
if (dist > tolerance) {
disableSpace = false;
}
}
if (disableSpace || mouseLeave || !lastMouse) return;
// user must move mouse or release space bar to allow another click
lastSpace = currSpace;
disableSpace = true;
d3_select(window).on('keyup.space-block', function() {
d3_event.preventDefault();
d3_event.stopPropagation();
disableSpace = false;
d3_select(window).on('keyup.space-block', null);
});
click();
}
function backspace() {
d3_event.preventDefault();
dispatch.call('undo');
}
function del() {
d3_event.preventDefault();
dispatch.call('cancel');
}
function ret() {
d3_event.preventDefault();
dispatch.call('finish');
}
function draw(selection) {
context.install(hover);
context.install(edit);
if (!context.inIntro() && !usedTails[tail.text()]) {
context.install(tail);
}
keybinding
.on('⌫', backspace)
.on('⌦', del)
.on('⎋', ret)
.on('↩', ret)
.on('space', space)
.on('⌥space', space);
selection
.on('mouseenter.draw', mouseenter)
.on('mouseleave.draw', mouseleave)
.on('mousedown.draw', mousedown)
.on('mousemove.draw', mousemove);
d3_select(document)
.call(keybinding);
return draw;
}
draw.off = function(selection) {
context.ui().sidebar.hover.cancel();
context.uninstall(hover);
context.uninstall(edit);
if (!context.inIntro() && !usedTails[tail.text()]) {
context.uninstall(tail);
usedTails[tail.text()] = true;
}
selection
.on('mouseenter.draw', null)
.on('mouseleave.draw', null)
.on('mousedown.draw', null)
.on('mousemove.draw', null);
d3_select(window)
.on('mouseup.draw', null);
// note: keyup.space-block, click.draw-block should remain
d3_select(document)
.call(keybinding.off);
};
draw.tail = function(_) {
tail.text(_);
return draw;
};
return utilRebind(draw, dispatch, 'on');
}