modules/behavior/draw.js (190 lines of code) (raw):

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 { schemaManager } from '../entities/schema_manager'; import { behaviorEdit } from './edit'; import { behaviorHover } from './hover'; import { behaviorTail } from './tail'; import { geoChooseEdge, geoVecLength } from '../geo'; import { utilKeybinding, utilRebind } from '../util'; var _usedTails = {}; var _disableSpace = false; var _lastSpace = null; export function behaviorDraw(context) { var dispatch = d3_dispatch( 'move', 'click', 'clickWay', 'clickNode', 'undo', 'cancel', 'finish' ); var keybinding = utilKeybinding('draw'); var _hover = behaviorHover(context).altDisables(true).ignoreVertex(true); var tail = behaviorTail(); var edit = behaviorEdit(context); var closeTolerance = 4; var tolerance = 12; var _mouseLeave = false; var _lastMouse = null; // related code // - `mode/drag_node.js` `datum()` function datum() { var mode = context.mode(); var isNote = mode && (mode.id.indexOf('note') !== -1); if (d3_event.altKey || isNote) return {}; var element; if (d3_event.type === 'keydown') { element = _lastMouse && _lastMouse.target; } else { element = d3_event.target; } // When drawing, snap only to touch targets.. // (this excludes area fills and active drawing elements) var d = element.__data__; return (d && d.properties && d.properties.target) ? d : {}; } 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); var touchId = d3_event.touches ? d3_event.changedTouches[0].identifier : null; var t1 = +new Date(); var p1 = point(); element.on('mousemove.draw', null); d3_select(window).on('mouseup.draw', function() { var t2 = +new Date(); var p2 = point(); var dist = geoVecLength(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; } // related code // - `mode/drag_node.js` `doMode()` // - `behavior/draw.js` `click()` // - `behavior/draw_way.js` `move()` function click() { var d = datum(); var target = d && d.properties && d.properties.entity; var mode = context.mode(); var targetGeometry = target && target.geometry(context.graph()); if (target && target.type === 'node') { // Snap to a node if (targetGeometry !== 'vertex' && !context.presets().allowsVertex(target, context.graph())) return; if (mode.id === 'add-point') { if (!schemaManager.canSnapNodeWithTagsToNode(mode.defaultTags, target, context.graph())) return; } dispatch.call('clickNode', this, target, d); } else if (target && target.type === 'way') { // Snap to a way if (mode.id === 'add-point') { if (!mode.preset.matchGeometry('vertex')) return; if (!schemaManager.canAddNodeWithTagsToWay(mode.defaultTags, target, context.graph())) return; } var choice = geoChooseEdge( context.childNodes(target), context.mouse(), context.projection, context.activeID() ); if (!choice) return; var edge = [target.nodes[choice.index - 1], target.nodes[choice.index]]; dispatch.call('clickWay', this, choice.loc, edge, d); } else { if (mode.id === 'add-point' && !mode.preset.matchGeometry('point')) return; dispatch.call('click', this, context.map().mouseCoordinates(), d); } } function space() { d3_event.preventDefault(); d3_event.stopPropagation(); var currSpace = context.mouse(); if (_disableSpace && _lastSpace) { var dist = geoVecLength(_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 behavior(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 behavior; } behavior.off = function(selection) { 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.unbind); }; behavior.tail = function(_) { tail.text(_); return behavior; }; behavior.hover = function() { return _hover; }; return utilRebind(behavior, dispatch, 'on'); }