/*
* Copyright 2020 WICKLETS LLC
*
* This file is part of Wick Engine.
*
* Wick Engine is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Wick Engine is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Wick Engine. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* A clipboard utility class for copy/paste functionality.
*/
Wick.Clipboard = class {
static get LOCALSTORAGE_KEY () {
return 'wick_engine_clipboard';
}
static get PASTE_OFFSET () {
// how many pixels should we shift objects over when we paste (canvas only)
return 20;
}
/**
* Create a new Clipboard object.
*/
constructor () {
this._copyLocation = null;
this._copyLayerIndex = 0;
this._originalObjects = [];
}
/**
* The data of copied objects, stored as JSON.
* @type {Object}
*/
get clipboardData () {
var json = localStorage[Wick.Clipboard.LOCALSTORAGE_KEY];
if(!json) return null;
return JSON.parse(json);
}
set clipboardData (clipboardData) {
localStorage[Wick.Clipboard.LOCALSTORAGE_KEY] = JSON.stringify(clipboardData);
}
/**
* Replace the current contents of the clipboard with new objects.
* @param {Wick.Base[]} objects - the objects to copy to the clipboard
*/
copyObjectsToClipboard (project, objects) {
if(!project || (!project instanceof Wick.Project)) console.error('copyObjectsToClipboard(): project is required');
// Get the playhead position of the "first" frame in the list of objects
var playheadCopyOffset = null;
objects.filter(object => {
return object instanceof Wick.Frame;
}).forEach(frame => {
if(playheadCopyOffset === null || frame.start < playheadCopyOffset) {
playheadCopyOffset = frame.start;
}
});
// Keep track of where objects were originally copied from
this._copyLocation = project.activeFrame && project.activeFrame.uuid;
// Keep track of the topmost layer of the selection (we use this later to position frames)
this._copyLayerIndex = Infinity;
objects.filter(object => {
return (object instanceof Wick.Frame)
|| (object instanceof Wick.Tween);
}).map(frame => {
return frame.parentLayer.index;
}).forEach(i => {
this._copyLayerIndex = Math.min(this._copyLayerIndex, i);
});
// Make deep copies of every object
var exportedData = objects.map(object => {
return object.export();
});
// Save references to the original objects
this._originalObjects = objects.map(object => {
return object;
});
// Shift frames and tweens so that they copy from the relative position of the first frame
var startPlayheadPosition = Number.MAX_SAFE_INTEGER;
exportedData.forEach(data => {
if(data.object.classname === 'Frame') {
if(data.object.start < startPlayheadPosition) {
startPlayheadPosition = data.object.start;
}
}
if(data.object.classname === 'Tween') {
if(data.object.playheadPosition < startPlayheadPosition) {
startPlayheadPosition = data.object.playheadPosition;
}
}
});
exportedData.forEach(data => {
if(data.object.classname === 'Frame') {
data.object.start -= startPlayheadPosition - 1;
data.object.end -= startPlayheadPosition - 1;
}
if(data.object.classname === 'Tween') {
data.object.playheadPosition -= startPlayheadPosition - 1;
}
});
// Set the new clipboard data
this.clipboardData = exportedData;
}
/**
* Paste the content of the clipboard into the project.
* @param {Wick.Project} project - the project to paste objects into.
* @returns {boolean} True if there is something to paste in the clipboard, false if the clipboard is empty.
*/
pasteObjectsFromClipboard (project) {
if(!project || (!project instanceof Wick.Project)) console.error('pasteObjectsFromClipboard(): project is required');
if(!this.clipboardData) {
return false;
}
// Prevent crash when pasting into an empty space
if(!project.activeFrame) {
project.insertBlankFrame();
}
// Always paste in-place if the original objects are no longer visible
var pasteInPlace = true;
this._originalObjects.forEach(origObj => {
if(origObj.parentFrame && origObj.parentFrame.onScreen) {
pasteInPlace = false;
}
});
// Use this value later to position frames on the corrent pasted layer
var layerIndicesMoved = project.activeLayer.index - this._copyLayerIndex;
project.selection.clear();
var objectsToSelect = [];
this.clipboardData.map(data => {
return Wick.Base.import(data, project).copy();
}).forEach(object => {
// Paste frames at the position of the playhead
if(object instanceof Wick.Frame) {
object._originalLayerIndex += layerIndicesMoved;
object.start += project.focus.timeline.playheadPosition - 1;
object.end += project.focus.timeline.playheadPosition - 1;
}
// Paste tweens at the position of the playhead
if(object instanceof Wick.Tween) {
object._originalLayerIndex += layerIndicesMoved;
object.playheadPosition += project.focus.timeline.playheadPosition - 1;
}
project.addObject(object);
object.identifier = object._getUniqueIdentifier(object.identifier);
// Add offset to Paths and Clips if pasteInPlace is NOT enabled.
if(!pasteInPlace && (object instanceof Wick.Path || object instanceof Wick.Clip)) {
object.view.render();//This render call updates the json, I think... so without this call the path loses its data somehow :(
object.x += Wick.Clipboard.PASTE_OFFSET;
object.y += Wick.Clipboard.PASTE_OFFSET;
}
// Wait to select objects.
objectsToSelect.push(object);
});
// Select newly added objects.
if (objectsToSelect.length > 0) {
project.selection.selectMultipleObjects(objectsToSelect);
}
return true;
}
}