Merge pull request #14235 from huffman/feat/edit-copy-paste

Add copy/paste functionality to edit.js
This commit is contained in:
tapalisa 2018-10-23 13:18:12 -07:00 committed by GitHub
commit 7a684081f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 226 additions and 19 deletions

View file

@ -133,7 +133,7 @@
{ "from": "Keyboard.W", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_FORWARD" },
{ "from": "Keyboard.S", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_BACKWARD" },
{ "from": "Keyboard.Shift", "when": ["!Keyboard.Left", "!Keyboard.Right"], "to": "Actions.SPRINT" },
{ "from": "Keyboard.C", "to": "Actions.VERTICAL_DOWN" },
{ "from": "Keyboard.C", "when": "!Keyboard.Control", "to": "Actions.VERTICAL_DOWN" },
{ "from": "Keyboard.Left", "when": "Keyboard.Shift", "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.Right", "when": "Keyboard.Shift", "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.Up", "when": "Application.CameraFirstPerson", "to": "Actions.LONGITUDINAL_FORWARD" },

View file

@ -1254,7 +1254,7 @@ function setupModelMenus() {
Menu.addMenuItem({
menuName: "Edit",
menuItemName: "Redo",
shortcutKey: 'Ctrl+Shift+Z',
shortcutKey: 'Ctrl+Y',
position: 1,
});
@ -1933,14 +1933,6 @@ function gridKey(value) {
}
}
}
var mapping = Controller.newMapping(CONTROLLER_MAPPING_NAME);
mapping.from([Controller.Hardware.Keyboard.Delete]).when([!Controller.Hardware.Application.PlatformMac]).to(deleteKey);
mapping.from([Controller.Hardware.Keyboard.Backspace]).when([Controller.Hardware.Application.PlatformMac]).to(deleteKey);
mapping.from([Controller.Hardware.Keyboard.D]).when([Controller.Hardware.Keyboard.Control]).to(deselectKey);
mapping.from([Controller.Hardware.Keyboard.T]).to(toggleKey);
mapping.from([Controller.Hardware.Keyboard.F]).to(focusKey);
mapping.from([Controller.Hardware.Keyboard.G]).to(gridKey);
function recursiveAdd(newParentID, parentData) {
if (parentData.children !== undefined) {
var children = parentData.children;
@ -2398,6 +2390,7 @@ var PropertiesTool = function (opts) {
return that;
};
var PopupMenu = function () {
var self = this;
@ -2567,6 +2560,46 @@ var PopupMenu = function () {
return this;
};
function whenPressed(fn) {
return function(value) {
if (value > 0) {
fn();
}
};
}
function whenReleased(fn) {
return function(value) {
if (value === 0) {
fn();
}
};
}
var mapping = Controller.newMapping(CONTROLLER_MAPPING_NAME);
mapping.from([Controller.Hardware.Keyboard.Delete]).when([!Controller.Hardware.Application.PlatformMac]).to(deleteKey);
mapping.from([Controller.Hardware.Keyboard.Backspace]).when([Controller.Hardware.Application.PlatformMac]).to(deleteKey);
mapping.from([Controller.Hardware.Keyboard.T]).to(toggleKey);
mapping.from([Controller.Hardware.Keyboard.F]).to(focusKey);
mapping.from([Controller.Hardware.Keyboard.G]).to(gridKey);
mapping.from([Controller.Hardware.Keyboard.X])
.when([Controller.Hardware.Keyboard.Control])
.to(whenReleased(function() { selectionManager.cutSelectedEntities() }));
mapping.from([Controller.Hardware.Keyboard.C])
.when([Controller.Hardware.Keyboard.Control])
.to(whenReleased(function() { selectionManager.copySelectedEntities() }));
mapping.from([Controller.Hardware.Keyboard.V])
.when([Controller.Hardware.Keyboard.Control])
.to(whenReleased(function() { selectionManager.pasteEntities() }));
mapping.from([Controller.Hardware.Keyboard.D])
.when([Controller.Hardware.Keyboard.Control])
.to(whenReleased(function() { selectionManager.duplicateSelection() }));
// Bind undo to ctrl-shift-z to maintain backwards-compatibility
mapping.from([Controller.Hardware.Keyboard.Z])
.when([Controller.Hardware.Keyboard.Control, Controller.Hardware.Keyboard.Shift])
.to(whenPressed(function() { undoHistory.redo() }));
var propertyMenu = new PopupMenu();

View file

@ -98,16 +98,18 @@ CameraManager = function() {
}
function getActionForKeyEvent(event) {
var action = keyToActionMapping[event.key];
if (action !== undefined) {
if (event.isShifted) {
if (action === "orbitForward") {
action = "orbitUp";
} else if (action === "orbitBackward") {
action = "orbitDown";
if (!event.isControl) {
var action = keyToActionMapping[event.key];
if (action !== undefined) {
if (event.isShifted) {
if (action === "orbitForward") {
action = "orbitUp";
} else if (action === "orbitBackward") {
action = "orbitDown";
}
}
return action;
}
return action;
}
return null;
}

View file

@ -26,6 +26,11 @@ Script.include([
"./utils.js"
]);
function deepCopy(v) {
return JSON.parse(JSON.stringify(v));
}
SelectionManager = (function() {
var that = {};
@ -199,9 +204,11 @@ SelectionManager = (function() {
}
};
// Return true if the given entity with `properties` is being grabbed by an avatar.
// Determine if an entity is being grabbed.
// This is mostly a heuristic - there is no perfect way to know if an entity is being
// grabbed.
//
// @return {boolean} true if the given entity with `properties` is being grabbed by an avatar
function nonDynamicEntityIsBeingGrabbedByAvatar(properties) {
if (properties.dynamic || Uuid.isNull(properties.parentID)) {
return false;
@ -228,6 +235,12 @@ SelectionManager = (function() {
return false;
}
var entityClipboard = {
entities: {}, // Map of id -> properties for copied entities
position: { x: 0, y: 0, z: 0 },
dimensions: { x: 0, y: 0, z: 0 },
};
that.duplicateSelection = function() {
var entitiesToDuplicate = [];
var duplicatedEntityIDs = [];
@ -305,6 +318,165 @@ SelectionManager = (function() {
return duplicatedEntityIDs;
};
// Create the entities in entityProperties, maintaining parent-child relationships.
// @param entityPropertites {array} - Array of entity property objects
that.createEntities = function(entityProperties) {
var entitiesToCreate = [];
var createdEntityIDs = [];
var createdChildrenWithOldParents = [];
var originalEntityToNewEntityID = [];
that.saveProperties();
for (var i = 0; i < entityProperties.length; ++i) {
var properties = entityProperties[i];
if (properties.parentID in originalEntityToNewEntityID) {
properties.parentID = originalEntityToNewEntityID[properties.parentID];
} else {
delete properties.parentID;
}
delete properties.actionData;
var newEntityID = Entities.addEntity(properties);
if (newEntityID) {
createdEntityIDs.push({
entityID: newEntityID,
properties: properties
});
if (properties.parentID !== Uuid.NULL) {
createdChildrenWithOldParents[newEntityID] = properties.parentID;
}
originalEntityToNewEntityID[properties.id] = newEntityID;
properties.id = newEntityID;
}
}
return createdEntityIDs;
}
that.cutSelectedEntities = function() {
copySelectedEntities();
deleteSelectedEntities();
}
that.copySelectedEntities = function() {
var entityProperties = Entities.getMultipleEntityProperties(that.selections);
var entities = {};
entityProperties.forEach(function(props) {
entities[props.id] = props;
});
function appendChildren(entityID, entities) {
var childrenIDs = Entities.getChildrenIDs(entityID);
for (var i = 0; i < childrenIDs.length; ++i) {
var id = childrenIDs[i];
if (!(id in entities)) {
entities[id] = Entities.getEntityProperties(id);
appendChildren(id, entities);
}
}
}
var len = entityProperties.length;
for (var i = 0; i < len; ++i) {
appendChildren(entityProperties[i].id, entities);
}
for (var id in entities) {
var parentID = entities[id].parentID;
entities[id].root = !(parentID in entities);
}
entityClipboard.entities = [];
var ids = Object.keys(entities);
while (ids.length > 0) {
// Go through all remaining entities.
// If an entity does not have a parent left, move it into the list
for (var i = 0; i < ids.length; ++i) {
var id = ids[i];
var parentID = entities[id].parentID;
if (parentID in entities) {
continue;
}
entityClipboard.entities.push(entities[id]);
delete entities[id];
}
ids = Object.keys(entities);
}
// Calculate size
if (entityClipboard.entities.length === 0) {
entityClipboard.dimensions = { x: 0, y: 0, z: 0 };
entityClipboard.position = { x: 0, y: 0, z: 0 };
} else {
var properties = entityClipboard.entities;
var brn = properties[0].boundingBox.brn;
var tfl = properties[0].boundingBox.tfl;
for (var i = 1; i < properties.length; i++) {
var bb = properties[i].boundingBox;
brn.x = Math.min(bb.brn.x, brn.x);
brn.y = Math.min(bb.brn.y, brn.y);
brn.z = Math.min(bb.brn.z, brn.z);
tfl.x = Math.max(bb.tfl.x, tfl.x);
tfl.y = Math.max(bb.tfl.y, tfl.y);
tfl.z = Math.max(bb.tfl.z, tfl.z);
}
entityClipboard.dimensions = {
x: tfl.x - brn.x,
y: tfl.y - brn.y,
z: tfl.z - brn.z
};
entityClipboard.position = {
x: brn.x + entityClipboard.dimensions.x / 2,
y: brn.y + entityClipboard.dimensions.y / 2,
z: brn.z + entityClipboard.dimensions.z / 2
};
}
}
that.pasteEntities = function() {
var dimensions = entityClipboard.dimensions;
var maxDimension = Math.max(dimensions.x, dimensions.y, dimensions.z);
var pastePosition = getPositionToCreateEntity(maxDimension);
var deltaPosition = Vec3.subtract(pastePosition, entityClipboard.position);
var copiedProperties = []
var ids = [];
entityClipboard.entities.forEach(function(originalProperties) {
var properties = deepCopy(originalProperties);
if (properties.root) {
properties.position = Vec3.sum(properties.position, deltaPosition);
delete properties.localPosition;
} else {
delete properties.position;
}
copiedProperties.push(properties);
});
var currentSelections = deepCopy(SelectionManager.selections);
function redo(copiedProperties) {
var created = that.createEntities(copiedProperties);
var ids = [];
for (var i = 0; i < created.length; ++i) {
ids.push(created[i].entityID);
}
SelectionManager.setSelections(ids);
}
function undo(copiedProperties) {
for (var i = 0; i < copiedProperties.length; ++i) {
Entities.deleteEntity(copiedProperties[i].id);
}
SelectionManager.setSelections(currentSelections);
}
redo(copiedProperties);
undoHistory.pushCommand(undo, copiedProperties, redo, copiedProperties);
}
that._update = function(selectionUpdated) {
var properties = null;
if (that.selections.length === 0) {