Merge pull request #7703 from ahocevar/draw-state
Improved drawing experience on touch devices
This commit is contained in:
@@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
### Next release
|
### Next release
|
||||||
|
|
||||||
|
#### Changed behavior of the `Draw` interaction
|
||||||
|
|
||||||
|
For better drawing experience, two changes were made to the behavior of the Draw interaction:
|
||||||
|
|
||||||
|
1. On long press, the current vertex can be dragged to its desired position.
|
||||||
|
2. On touch move (e.g. when panning the map on a mobile device), no draw cursor is shown, and the geometry being drawn is not updated. But because of 1., the draw cursor will appear on long press. Mouse moves are not affected by this change.
|
||||||
|
|
||||||
#### Changes in proj4 integration
|
#### Changes in proj4 integration
|
||||||
|
|
||||||
Because relying on a globally available proj4 is not practical with ES modules, we have made a change to the way we integrate proj4:
|
Because relying on a globally available proj4 is not practical with ES modules, we have made a change to the way we integrate proj4:
|
||||||
|
|||||||
@@ -2356,6 +2356,7 @@ olx.interaction.DragZoomOptions.prototype.out;
|
|||||||
* @typedef {{clickTolerance: (number|undefined),
|
* @typedef {{clickTolerance: (number|undefined),
|
||||||
* features: (ol.Collection.<ol.Feature>|undefined),
|
* features: (ol.Collection.<ol.Feature>|undefined),
|
||||||
* source: (ol.source.Vector|undefined),
|
* source: (ol.source.Vector|undefined),
|
||||||
|
* dragVertexDelay: (number|undefined),
|
||||||
* snapTolerance: (number|undefined),
|
* snapTolerance: (number|undefined),
|
||||||
* type: (ol.geom.GeometryType|string),
|
* type: (ol.geom.GeometryType|string),
|
||||||
* stopClick: (boolean|undefined),
|
* stopClick: (boolean|undefined),
|
||||||
@@ -2401,6 +2402,14 @@ olx.interaction.DrawOptions.prototype.features;
|
|||||||
olx.interaction.DrawOptions.prototype.source;
|
olx.interaction.DrawOptions.prototype.source;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delay in milliseconds after pointerdown before the current vertex can be
|
||||||
|
* dragged to its exact position. Default is 500 ms.
|
||||||
|
* @type {number|undefined}
|
||||||
|
*/
|
||||||
|
olx.interaction.DrawOptions.prototype.dragVertexDelay;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pixel distance for snapping to the drawing finish. Default is `12`.
|
* Pixel distance for snapping to the drawing finish. Default is `12`.
|
||||||
* @type {number|undefined}
|
* @type {number|undefined}
|
||||||
|
|||||||
@@ -267,6 +267,8 @@ const PluggableMap = function(options) {
|
|||||||
*/
|
*/
|
||||||
this.keyHandlerKeys_ = null;
|
this.keyHandlerKeys_ = null;
|
||||||
|
|
||||||
|
_ol_events_.listen(this.viewport_, EventType.CONTEXTMENU,
|
||||||
|
this.handleBrowserEvent, this);
|
||||||
_ol_events_.listen(this.viewport_, EventType.WHEEL,
|
_ol_events_.listen(this.viewport_, EventType.WHEEL,
|
||||||
this.handleBrowserEvent, this);
|
this.handleBrowserEvent, this);
|
||||||
_ol_events_.listen(this.viewport_, EventType.MOUSEWHEEL,
|
_ol_events_.listen(this.viewport_, EventType.MOUSEWHEEL,
|
||||||
@@ -491,6 +493,8 @@ PluggableMap.prototype.addOverlayInternal_ = function(overlay) {
|
|||||||
*/
|
*/
|
||||||
PluggableMap.prototype.disposeInternal = function() {
|
PluggableMap.prototype.disposeInternal = function() {
|
||||||
this.mapBrowserEventHandler_.dispose();
|
this.mapBrowserEventHandler_.dispose();
|
||||||
|
_ol_events_.unlisten(this.viewport_, EventType.CONTEXTMENU,
|
||||||
|
this.handleBrowserEvent, this);
|
||||||
_ol_events_.unlisten(this.viewport_, EventType.WHEEL,
|
_ol_events_.unlisten(this.viewport_, EventType.WHEEL,
|
||||||
this.handleBrowserEvent, this);
|
this.handleBrowserEvent, this);
|
||||||
_ol_events_.unlisten(this.viewport_, EventType.MOUSEWHEEL,
|
_ol_events_.unlisten(this.viewport_, EventType.MOUSEWHEEL,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export default {
|
|||||||
CHANGE: 'change',
|
CHANGE: 'change',
|
||||||
|
|
||||||
CLEAR: 'clear',
|
CLEAR: 'clear',
|
||||||
|
CONTEXTMENU: 'contextmenu',
|
||||||
CLICK: 'click',
|
CLICK: 'click',
|
||||||
DBLCLICK: 'dblclick',
|
DBLCLICK: 'dblclick',
|
||||||
DRAGENTER: 'dragenter',
|
DRAGENTER: 'dragenter',
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
* @module ol/interaction/Draw
|
* @module ol/interaction/Draw
|
||||||
*/
|
*/
|
||||||
import {inherits} from '../index.js';
|
import {inherits} from '../index.js';
|
||||||
|
import EventType from '../events/EventType.js';
|
||||||
import Feature from '../Feature.js';
|
import Feature from '../Feature.js';
|
||||||
import MapBrowserEventType from '../MapBrowserEventType.js';
|
import MapBrowserEventType from '../MapBrowserEventType.js';
|
||||||
|
import MapBrowserPointerEvent from '../MapBrowserPointerEvent.js';
|
||||||
import BaseObject from '../Object.js';
|
import BaseObject from '../Object.js';
|
||||||
import _ol_coordinate_ from '../coordinate.js';
|
import _ol_coordinate_ from '../coordinate.js';
|
||||||
import _ol_events_ from '../events.js';
|
import _ol_events_ from '../events.js';
|
||||||
@@ -17,6 +19,7 @@ import LineString from '../geom/LineString.js';
|
|||||||
import MultiLineString from '../geom/MultiLineString.js';
|
import MultiLineString from '../geom/MultiLineString.js';
|
||||||
import MultiPoint from '../geom/MultiPoint.js';
|
import MultiPoint from '../geom/MultiPoint.js';
|
||||||
import MultiPolygon from '../geom/MultiPolygon.js';
|
import MultiPolygon from '../geom/MultiPolygon.js';
|
||||||
|
import MouseSource from '../pointer/MouseSource.js';
|
||||||
import Point from '../geom/Point.js';
|
import Point from '../geom/Point.js';
|
||||||
import Polygon, {fromCircle, makeRegular} from '../geom/Polygon.js';
|
import Polygon, {fromCircle, makeRegular} from '../geom/Polygon.js';
|
||||||
import DrawEventType from '../interaction/DrawEventType.js';
|
import DrawEventType from '../interaction/DrawEventType.js';
|
||||||
@@ -56,6 +59,18 @@ const Draw = function(options) {
|
|||||||
*/
|
*/
|
||||||
this.downPx_ = null;
|
this.downPx_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.downTimeout_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.lastDragTime_;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
* @private
|
* @private
|
||||||
@@ -191,6 +206,12 @@ const Draw = function(options) {
|
|||||||
*/
|
*/
|
||||||
this.geometryFunction_ = geometryFunction;
|
this.geometryFunction_ = geometryFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.dragVertexDelay_ = options.dragVertexDelay !== undefined ? options.dragVertexDelay : 500;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finish coordinate for the feature (first point for polygons, last point for
|
* Finish coordinate for the feature (first point for polygons, last point for
|
||||||
* linestrings).
|
* linestrings).
|
||||||
@@ -255,7 +276,8 @@ const Draw = function(options) {
|
|||||||
wrapX: options.wrapX ? options.wrapX : false
|
wrapX: options.wrapX ? options.wrapX : false
|
||||||
}),
|
}),
|
||||||
style: options.style ? options.style :
|
style: options.style ? options.style :
|
||||||
Draw.getDefaultStyleFunction()
|
Draw.getDefaultStyleFunction(),
|
||||||
|
updateWhileInteracting: true
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -322,8 +344,27 @@ Draw.prototype.setMap = function(map) {
|
|||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
Draw.handleEvent = function(event) {
|
Draw.handleEvent = function(event) {
|
||||||
|
if (event.originalEvent.type === EventType.CONTEXTMENU) {
|
||||||
|
// Avoid context menu for long taps when drawing on mobile
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
this.freehand_ = this.mode_ !== Draw.Mode_.POINT && this.freehandCondition_(event);
|
this.freehand_ = this.mode_ !== Draw.Mode_.POINT && this.freehandCondition_(event);
|
||||||
|
let move = event.type === MapBrowserEventType.POINTERMOVE;
|
||||||
let pass = true;
|
let pass = true;
|
||||||
|
if (this.lastDragTime_ && event.type === MapBrowserEventType.POINTERDRAG) {
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - this.lastDragTime_ >= this.dragVertexDelay_) {
|
||||||
|
this.downPx_ = event.pixel;
|
||||||
|
this.shouldHandle_ = !this.freehand_;
|
||||||
|
move = true;
|
||||||
|
} else {
|
||||||
|
this.lastDragTime_ = undefined;
|
||||||
|
}
|
||||||
|
if (this.shouldHandle_ && this.downTimeout_) {
|
||||||
|
clearTimeout(this.downTimeout_);
|
||||||
|
this.downTimeout_ = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (this.freehand_ &&
|
if (this.freehand_ &&
|
||||||
event.type === MapBrowserEventType.POINTERDRAG &&
|
event.type === MapBrowserEventType.POINTERDRAG &&
|
||||||
this.sketchFeature_ !== null) {
|
this.sketchFeature_ !== null) {
|
||||||
@@ -332,11 +373,18 @@ Draw.handleEvent = function(event) {
|
|||||||
} else if (this.freehand_ &&
|
} else if (this.freehand_ &&
|
||||||
event.type === MapBrowserEventType.POINTERDOWN) {
|
event.type === MapBrowserEventType.POINTERDOWN) {
|
||||||
pass = false;
|
pass = false;
|
||||||
} else if (event.type === MapBrowserEventType.POINTERMOVE) {
|
} else if (move) {
|
||||||
pass = this.handlePointerMove_(event);
|
pass = event.type === MapBrowserEventType.POINTERMOVE;
|
||||||
|
if (pass && this.freehand_) {
|
||||||
|
pass = this.handlePointerMove_(event);
|
||||||
|
} else if (event.pointerEvent.pointerType == MouseSource.POINTER_TYPE ||
|
||||||
|
(event.type === MapBrowserEventType.POINTERDRAG && !this.downTimeout_)) {
|
||||||
|
this.handlePointerMove_(event);
|
||||||
|
}
|
||||||
} else if (event.type === MapBrowserEventType.DBLCLICK) {
|
} else if (event.type === MapBrowserEventType.DBLCLICK) {
|
||||||
pass = false;
|
pass = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return PointerInteraction.handleEvent.call(this, event) && pass;
|
return PointerInteraction.handleEvent.call(this, event) && pass;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -357,6 +405,11 @@ Draw.handleDownEvent_ = function(event) {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else if (this.condition_(event)) {
|
} else if (this.condition_(event)) {
|
||||||
|
this.lastDragTime_ = Date.now();
|
||||||
|
this.downTimeout_ = setTimeout(function() {
|
||||||
|
this.handlePointerMove_(new MapBrowserPointerEvent(
|
||||||
|
MapBrowserEventType.POINTERMOVE, event.map, event.pointerEvent, event.frameState));
|
||||||
|
}.bind(this), this.dragVertexDelay_);
|
||||||
this.downPx_ = event.pixel;
|
this.downPx_ = event.pixel;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@@ -374,6 +427,11 @@ Draw.handleDownEvent_ = function(event) {
|
|||||||
Draw.handleUpEvent_ = function(event) {
|
Draw.handleUpEvent_ = function(event) {
|
||||||
let pass = true;
|
let pass = true;
|
||||||
|
|
||||||
|
if (this.downTimeout_) {
|
||||||
|
clearTimeout(this.downTimeout_);
|
||||||
|
this.downTimeout_ = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
this.handlePointerMove_(event);
|
this.handlePointerMove_(event);
|
||||||
|
|
||||||
const circleMode = this.mode_ === Draw.Mode_.CIRCLE;
|
const circleMode = this.mode_ === Draw.Mode_.CIRCLE;
|
||||||
@@ -423,6 +481,9 @@ Draw.prototype.handlePointerMove_ = function(event) {
|
|||||||
this.shouldHandle_ = this.freehand_ ?
|
this.shouldHandle_ = this.freehand_ ?
|
||||||
squaredDistance > this.squaredClickTolerance_ :
|
squaredDistance > this.squaredClickTolerance_ :
|
||||||
squaredDistance <= this.squaredClickTolerance_;
|
squaredDistance <= this.squaredClickTolerance_;
|
||||||
|
if (!this.shouldHandle_) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.finishCoordinate_) {
|
if (this.finishCoordinate_) {
|
||||||
@@ -505,9 +566,6 @@ Draw.prototype.startDrawing_ = function(event) {
|
|||||||
this.sketchLineCoords_ = this.sketchCoords_[0];
|
this.sketchLineCoords_ = this.sketchCoords_[0];
|
||||||
} else {
|
} else {
|
||||||
this.sketchCoords_ = [start.slice(), start.slice()];
|
this.sketchCoords_ = [start.slice(), start.slice()];
|
||||||
if (this.mode_ === Draw.Mode_.CIRCLE) {
|
|
||||||
this.sketchLineCoords_ = this.sketchCoords_;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (this.sketchLineCoords_) {
|
if (this.sketchLineCoords_) {
|
||||||
this.sketchLine_ = new Feature(
|
this.sketchLine_ = new Feature(
|
||||||
|
|||||||
@@ -103,6 +103,15 @@ describe('ol.interaction.Draw', function() {
|
|||||||
expect(draw.freehandCondition_(event)).to.be(true);
|
expect(draw.freehandCondition_(event)).to.be(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('accepts a dragVertexDelay option', function() {
|
||||||
|
const draw = new Draw({
|
||||||
|
source: source,
|
||||||
|
type: 'LineString',
|
||||||
|
dragVertexDelay: 42
|
||||||
|
});
|
||||||
|
expect(draw.dragVertexDelay_).to.be(42);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('specifying a geometryName', function() {
|
describe('specifying a geometryName', function() {
|
||||||
@@ -339,7 +348,6 @@ describe('ol.interaction.Draw', function() {
|
|||||||
simulateEvent('pointermove', 20, 30);
|
simulateEvent('pointermove', 20, 30);
|
||||||
|
|
||||||
// freehand
|
// freehand
|
||||||
simulateEvent('pointerdown', 20, 30, true);
|
|
||||||
simulateEvent('pointermove', 20, 30, true);
|
simulateEvent('pointermove', 20, 30, true);
|
||||||
simulateEvent('pointerdrag', 20, 30, true);
|
simulateEvent('pointerdrag', 20, 30, true);
|
||||||
simulateEvent('pointermove', 30, 40, true);
|
simulateEvent('pointermove', 30, 40, true);
|
||||||
@@ -396,6 +404,38 @@ describe('ol.interaction.Draw', function() {
|
|||||||
expect(geometry.getCoordinates()).to.eql([[10, -20], [30, -20]]);
|
expect(geometry.getCoordinates()).to.eql([[10, -20], [30, -20]]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('allows dragging of the vertex after dragVertexDelay', function(done) {
|
||||||
|
// first point
|
||||||
|
simulateEvent('pointermove', 10, 20);
|
||||||
|
simulateEvent('pointerdown', 10, 20);
|
||||||
|
simulateEvent('pointerup', 10, 20);
|
||||||
|
|
||||||
|
// second point, drag vertex
|
||||||
|
simulateEvent('pointermove', 15, 20);
|
||||||
|
simulateEvent('pointerdown', 15, 20);
|
||||||
|
setTimeout(function() {
|
||||||
|
simulateEvent('pointermove', 20, 10);
|
||||||
|
simulateEvent('pointerdrag', 20, 10);
|
||||||
|
simulateEvent('pointerup', 20, 10);
|
||||||
|
// third point
|
||||||
|
simulateEvent('pointermove', 30, 20);
|
||||||
|
simulateEvent('pointerdown', 30, 20);
|
||||||
|
simulateEvent('pointerup', 30, 20);
|
||||||
|
|
||||||
|
// finish on third point
|
||||||
|
simulateEvent('pointerdown', 30, 20);
|
||||||
|
simulateEvent('pointerup', 30, 20);
|
||||||
|
|
||||||
|
const features = source.getFeatures();
|
||||||
|
expect(features).to.have.length(1);
|
||||||
|
const geometry = features[0].getGeometry();
|
||||||
|
expect(geometry).to.be.a(LineString);
|
||||||
|
expect(geometry.getCoordinates()).to.eql([[10, -20], [20, -10], [30, -20]]);
|
||||||
|
|
||||||
|
done();
|
||||||
|
}, 600);
|
||||||
|
});
|
||||||
|
|
||||||
it('triggers draw events', function() {
|
it('triggers draw events', function() {
|
||||||
const ds = sinon.spy();
|
const ds = sinon.spy();
|
||||||
const de = sinon.spy();
|
const de = sinon.spy();
|
||||||
|
|||||||
Reference in New Issue
Block a user