Allow rotate of multiple selected objects

(closes #1719)
This commit is contained in:
Bryan Housel
2016-12-21 16:44:40 -05:00
parent cad4c0090c
commit 38e4900355
2 changed files with 97 additions and 81 deletions
+74 -60
View File
@@ -1,13 +1,8 @@
import * as d3 from 'd3';
import _ from 'lodash';
import { d3keybinding } from '../lib/d3.keybinding.js';
import { t } from '../util/locale';
import {
actionNoop,
actionRotate
} from '../actions/index';
import { actionRotate } from '../actions/index';
import { behaviorEdit } from '../behavior/index';
import {
@@ -24,8 +19,15 @@ import {
operationReflectShort
} from '../operations/index';
import {
polygonHull as d3polygonHull,
polygonCentroid as d3polygonCentroid
} from 'd3';
export function modeRotate(context, wayId) {
import { utilGetAllNodes } from '../util';
export function modeRotate(context, entityIDs) {
var mode = {
id: 'rotate',
button: 'browse'
@@ -34,33 +36,82 @@ export function modeRotate(context, wayId) {
var keybinding = d3keybinding('rotate'),
behaviors = [
behaviorEdit(context),
operationCircularize([wayId], context).behavior,
operationDelete([wayId], context).behavior,
operationMove([wayId], context).behavior,
operationOrthogonalize([wayId], context).behavior,
operationReflectLong([wayId], context).behavior,
operationReflectShort([wayId], context).behavior
operationCircularize(entityIDs, context).behavior,
operationDelete(entityIDs, context).behavior,
operationMove(entityIDs, context).behavior,
operationOrthogonalize(entityIDs, context).behavior,
operationReflectLong(entityIDs, context).behavior,
operationReflectShort(entityIDs, context).behavior
],
annotation = entityIDs.length === 1 ?
t('operations.rotate.annotation.' + context.geometry(entityIDs[0])) :
t('operations.move.annotation.multiple'),
prevGraph,
prevAngle,
prevTransform,
pivot;
function doRotate() {
var fn;
if (context.graph() !== prevGraph) {
fn = context.perform;
} else {
fn = context.replace;
}
// projection changed, recalculate pivot
var projection = context.projection;
var currTransform = projection.transform();
if (!prevTransform ||
currTransform.k !== prevTransform.k ||
currTransform.x !== prevTransform.x ||
currTransform.y !== prevTransform.y) {
var nodes = utilGetAllNodes(entityIDs, context.graph()),
points = nodes.map(function(n) { return projection(n.loc); });
pivot = d3polygonCentroid(d3polygonHull(points));
prevAngle = undefined;
}
var currMouse = context.mouse(),
currAngle = Math.atan2(currMouse[1] - pivot[1], currMouse[0] - pivot[0]);
if (typeof prevAngle === 'undefined') prevAngle = currAngle;
var delta = currAngle - prevAngle;
fn(actionRotate(entityIDs, pivot, delta, projection), annotation);
prevTransform = currTransform;
prevAngle = currAngle;
prevGraph = context.graph();
}
function finish() {
d3.event.stopPropagation();
context.enter(modeSelect(context, entityIDs).suppressMenu(true));
}
function cancel() {
context.pop();
context.enter(modeSelect(context, entityIDs).suppressMenu(true));
}
function undone() {
context.enter(modeBrowse(context));
}
mode.enter = function() {
var way = context.graph().entity(wayId),
nodes = _.uniq(context.graph().childNodes(way)),
points = nodes.map(function(n) { return context.projection(n.loc); });
pivot = d3.polygonCentroid(points);
behaviors.forEach(function(behavior) {
context.install(behavior);
});
var annotation = t('operations.rotate.annotation.' + context.geometry(wayId));
context.perform(actionNoop(), annotation);
context.surface()
.on('mousemove.rotate', doRotate)
.on('click.rotate', finish);
@@ -74,43 +125,6 @@ export function modeRotate(context, wayId) {
d3.select(document)
.call(keybinding);
function doRotate() {
var fn;
if (prevGraph !== context.graph()) {
fn = context.perform;
} else {
fn = context.replace;
}
var currMouse = context.mouse(),
currAngle = Math.atan2(currMouse[1] - pivot[1], currMouse[0] - pivot[0]);
if (typeof prevAngle === 'undefined') prevAngle = currAngle;
var delta = currAngle - prevAngle;
fn(actionRotate(wayId, pivot, delta, context.projection), annotation);
prevAngle = currAngle;
prevGraph = context.graph();
}
function finish() {
d3.event.stopPropagation();
context.enter(modeSelect(context, [wayId]).suppressMenu(true));
}
function cancel() {
context.pop();
context.enter(modeSelect(context, [wayId]).suppressMenu(true));
}
function undone() {
context.enter(modeBrowse(context));
}
};
+23 -21
View File
@@ -1,39 +1,41 @@
import _ from 'lodash';
import { t } from '../util/locale';
import { modeRotate } from '../modes/index';
import { behaviorOperation } from '../behavior/index';
import { geoExtent } from '../geo/index';
import { modeRotate } from '../modes/index';
export function operationRotate(selectedIDs, context) {
var entityId = selectedIDs[0],
entity = context.entity(entityId),
extent = entity.extent(context.graph()),
geometry = context.geometry(entityId);
var extent = selectedIDs.reduce(function(extent, id) {
return extent.extend(context.entity(id).extent(context.graph()));
}, geoExtent());
var operation = function() {
context.enter(modeRotate(context, entityId));
context.enter(modeRotate(context, selectedIDs));
};
operation.available = function() {
if (selectedIDs.length !== 1 || entity.type !== 'way')
return false;
if (geometry === 'area')
return true;
if (entity.isClosed() &&
context.graph().parentRelations(entity).some(function(r) { return r.isMultipolygon(); }))
return true;
return false;
return selectedIDs.length > 1 ||
context.entity(selectedIDs[0]).type !== 'node';
};
operation.disabled = function() {
if (extent.percentContainedIn(context.extent()) < 0.8) {
return 'too_large';
} else if (context.hasHiddenConnections(entityId)) {
return 'connected_to_hidden';
} else {
return false;
var reason;
if (extent.area() && extent.percentContainedIn(context.extent()) < 0.8) {
reason = 'too_large';
} else if (_.some(selectedIDs, context.hasHiddenConnections)) {
reason = 'connected_to_hidden';
} else if (_.some(selectedIDs, incompleteRelation)) {
reason = 'incomplete_relation';
}
return reason;
function incompleteRelation(id) {
var entity = context.entity(id);
return entity.type === 'relation' && !entity.isComplete(graph);
}
};
@@ -42,7 +44,7 @@ export function operationRotate(selectedIDs, context) {
var disable = operation.disabled();
return disable ?
t('operations.rotate.' + disable) :
t('operations.rotate.description');
t('operations.rotate.description.' + (selectedIDs.length === 1 ? 'single' : 'multiple'));
};