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

import { dispatch as d3_dispatch } from 'd3-dispatch'; import { select as d3_select } from 'd3-selection'; import { presetManager } from '../presets'; import { behaviorEdit } from './edit'; import { behaviorHover } from './hover'; import { geoChooseEdge } from '../geo'; import { utilFastMouse, utilKeybinding, utilRebind } from '../util'; import { vecLength } from '@id-sdk/vector'; var _disableSpace = false; var _lastSpace = null; export function behaviorDraw(context) { var dispatch = d3_dispatch( 'move', 'down', 'downcancel', 'click', 'clickWay', 'clickNode', 'undo', 'cancel', 'finish' ); var keybinding = utilKeybinding('draw'); var _hover = behaviorHover(context) .altDisables(true) .ignoreVertex(true) .on('hover', context.ui().sidebar.hover); var _edit = behaviorEdit(context); var _closeTolerance = 4; var _tolerance = 12; var _mouseLeave = false; var _lastMouse = null; var _lastPointerUpEvent; var _downPointer; // use pointer events on supported platforms; fallback to mouse events var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse'; // related code // - `mode/drag_node.js` `datum()` function datum(d3_event) { 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 pointerdown(d3_event) { if (_downPointer) return; var pointerLocGetter = utilFastMouse(this); _downPointer = { id: d3_event.pointerId || 'mouse', pointerLocGetter: pointerLocGetter, downTime: +new Date(), downLoc: pointerLocGetter(d3_event) }; dispatch.call('down', this, d3_event, datum(d3_event)); } function pointerup(d3_event) { if (!_downPointer || _downPointer.id !== (d3_event.pointerId || 'mouse')) return; var downPointer = _downPointer; _downPointer = null; _lastPointerUpEvent = d3_event; if (downPointer.isCancelled) return; var t2 = +new Date(); var p2 = downPointer.pointerLocGetter(d3_event); var dist = vecLength(downPointer.downLoc, p2); if (dist < _closeTolerance || (dist < _tolerance && (t2 - downPointer.downTime) < 500)) { // Prevent a quick second click d3_select(window).on('click.draw-block', function() { d3_event.stopPropagation(); }, true); context.map().dblclickZoomEnable(false); window.setTimeout(function() { context.map().dblclickZoomEnable(true); d3_select(window).on('click.draw-block', null); }, 500); click(d3_event, p2); } } function pointermove(d3_event) { if (_downPointer && _downPointer.id === (d3_event.pointerId || 'mouse') && !_downPointer.isCancelled) { var p2 = _downPointer.pointerLocGetter(d3_event); var dist = vecLength(_downPointer.downLoc, p2); if (dist >= _closeTolerance) { _downPointer.isCancelled = true; dispatch.call('downcancel', this); } } if ((d3_event.pointerType && d3_event.pointerType !== 'mouse') || d3_event.buttons || _downPointer) return; // HACK: Mobile Safari likes to send one or more `mouse` type pointermove // events immediately after non-mouse pointerup events; detect and ignore them. if (_lastPointerUpEvent && _lastPointerUpEvent.pointerType !== 'mouse' && d3_event.timeStamp - _lastPointerUpEvent.timeStamp < 100) return; _lastMouse = d3_event; dispatch.call('move', this, d3_event, datum(d3_event)); } function pointercancel(d3_event) { if (_downPointer && _downPointer.id === (d3_event.pointerId || 'mouse')) { if (!_downPointer.isCancelled) { dispatch.call('downcancel', this); } _downPointer = null; } } function mouseenter() { _mouseLeave = false; } function mouseleave() { _mouseLeave = true; } function allowsVertex(d) { return d.geometry(context.graph()) === 'vertex' || presetManager.allowsVertex(d, context.graph()); } // related code // - `mode/drag_node.js` `doMove()` // - `behavior/draw.js` `click()` // - `behavior/draw_way.js` `move()` function click(d3_event, loc) { var d = datum(d3_event); var target = d && d.properties && d.properties.entity; var mode = context.mode(); if (target && target.type === 'node' && allowsVertex(target)) { // Snap to a node dispatch.call('clickNode', this, target, d); return; } else if (target && target.type === 'way' && (mode.id !== 'add-point' || mode.preset.matchGeometry('vertex'))) { // Snap to a way var choice = geoChooseEdge( context.graph().childNodes(target), loc, context.projection, context.activeID() ); if (choice) { var edge = [target.nodes[choice.index - 1], target.nodes[choice.index]]; dispatch.call('clickWay', this, choice.loc, edge, d); return; } } else if (mode.id !== 'add-point' || mode.preset.matchGeometry('point')) { var locLatLng = context.projection.invert(loc); dispatch.call('click', this, locLatLng, d); } } // treat a spacebar press like a click function space(d3_event) { d3_event.preventDefault(); d3_event.stopPropagation(); var currSpace = context.map().mouse(); if (_disableSpace && _lastSpace) { var dist = vecLength(_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); }); // get the current mouse position var loc = context.map().mouse() || // or the map center if the mouse has never entered the map context.projection(context.map().center()); click(d3_event, loc); } function backspace(d3_event) { d3_event.preventDefault(); dispatch.call('undo'); } function del(d3_event) { d3_event.preventDefault(); dispatch.call('cancel'); } function ret(d3_event) { d3_event.preventDefault(); dispatch.call('finish'); } function behavior(selection) { context.install(_hover); context.install(_edit); _downPointer = null; 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(_pointerPrefix + 'down.draw', pointerdown) .on(_pointerPrefix + 'move.draw', pointermove); d3_select(window) .on(_pointerPrefix + 'up.draw', pointerup, true) .on('pointercancel.draw', pointercancel, true); d3_select(document) .call(keybinding); return behavior; } behavior.off = function(selection) { context.ui().sidebar.hover.cancel(); context.uninstall(_hover); context.uninstall(_edit); selection .on('mouseenter.draw', null) .on('mouseleave.draw', null) .on(_pointerPrefix + 'down.draw', null) .on(_pointerPrefix + 'move.draw', null); d3_select(window) .on(_pointerPrefix + 'up.draw', null) .on('pointercancel.draw', null); // note: keyup.space-block, click.draw-block should remain d3_select(document) .call(keybinding.unbind); }; behavior.hover = function() { return _hover; }; return utilRebind(behavior, dispatch, 'on'); }