From 9301b7a984a49e204ab322af6cc68854069a3287 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 20 Jan 2023 21:54:33 +1100 Subject: [PATCH 1/2] Add single image export function --- engine/dist/wickengine.js | 5829 ++++++----------- engine/src/base/Project.js | 124 + engine/src/export/image/ImageFile.js | 53 + public/corelibs/wick-engine/emptyproject.html | 5829 ++++++----------- public/corelibs/wick-engine/wickengine.js | 5829 ++++++----------- src/Editor/EditorCore.jsx | 64 + src/Editor/EditorWrapper.jsx | 1 + .../Modals/ExportOptions/ExportOptions.jsx | 19 +- .../Modals/ModalHandler/ModalHandler.jsx | 1 + 9 files changed, 6458 insertions(+), 11291 deletions(-) create mode 100644 engine/src/export/image/ImageFile.js diff --git a/engine/dist/wickengine.js b/engine/dist/wickengine.js index 9566efc2e..a8d44fa8c 100644 --- a/engine/dist/wickengine.js +++ b/engine/dist/wickengine.js @@ -1,5 +1,5 @@ /*Wick Engine https://github.com/Wicklets/wick-engine*/ -var WICK_ENGINE_BUILD_VERSION = "2021.1.18.12.6.20"; +var WICK_ENGINE_BUILD_VERSION = "2023.1.20.21.34.33"; /*! * Paper.js v0.12.4 - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ @@ -45737,10 +45737,11 @@ Wick = { version: window.WICK_ENGINE_BUILD_VERSION || "dev", resourcepath: '../dist/', _originals: {} // Eventually store a single instance of each type of Wick.Base object (see Wick.Base constructor). - }; -console.log('Wick Engine version "' + Wick.version + '" is available.'); // Ensure that the Wick namespace is accessible in environments where globals are finicky (react, webpack, etc) +console.log('Wick Engine version "' + Wick.version + '" is available.'); + +// Ensure that the Wick namespace is accessible in environments where globals are finicky (react, webpack, etc) window.Wick = Wick; /* * Copyright 2020 WICKLETS LLC @@ -45768,45 +45769,41 @@ 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 + 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; @@ -45814,10 +45811,12 @@ Wick.Clipboard = class { 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) + // 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; @@ -45825,16 +45824,19 @@ Wick.Clipboard = class { return frame.parentLayer.index; }).forEach(i => { this._copyLayerIndex = Math.min(this._copyLayerIndex, i); - }); // Make deep copies of every object + }); + // Make deep copies of every object var exportedData = objects.map(object => { return object.export(); - }); // Save references to the original objects + }); + // 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 + }); + // 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') { @@ -45842,7 +45844,6 @@ Wick.Clipboard = class { startPlayheadPosition = data.object.start; } } - if (data.object.classname === 'Tween') { if (data.object.playheadPosition < startPlayheadPosition) { startPlayheadPosition = data.object.playheadPosition; @@ -45854,43 +45855,40 @@ Wick.Clipboard = class { 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 + }); + // 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 - + } + // 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 - + } + // 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 - + }); + // Use this value later to position frames on the corrent pasted layer var layerIndicesMoved = project.activeLayer.index - this._copyLayerIndex; project.selection.clear(); var objectsToSelect = []; @@ -45902,35 +45900,33 @@ Wick.Clipboard = class { 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 - + } + // 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. + 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. - + } + // Wait to select objects. objectsToSelect.push(object); - }); // Select newly added objects. + }); + // Select newly added objects. if (objectsToSelect.length > 0) { project.selection.selectMultipleObjects(objectsToSelect); } - return true; } - }; /* * Copyright 2020 WICKLETS LLC @@ -45964,82 +45960,71 @@ Wick.Color = class { this._color = new paper.Color(); } } + /** * The red value of the color. Ranges from 0.0 to 1.0. * @type {Number} */ - - get r() { return this._color.red; } - set r(r) { this._color.red = r; } + /** * The green value of the color. Ranges from 0.0 to 1.0. * @type {Number} */ - - get g() { return this._color.green; } - set g(g) { this._color.green = g; } + /** * The blue value of the color. Ranges from 0.0 to 1.0. * @type {Number} */ - - get b() { return this._color.blue; } - set b(b) { this._color.blue = b; } + /** * The alpha value of the color. Ranges from 0.0 to 1.0. * @type {Number} */ - - get a() { return this._color.alpha; } - set a(a) { this._color.alpha = a; } + /** * The color as a hex string. Example: "#AABBCC" * @type {String} */ - - get hex() { return this._color.toCSS(true); } + /** * The color as an rgba string. Example: "rgba(r,g,b,a)" */ - - get rgba() { return this._color.toCSS(); } + /** * Adds together the r, g, and b values of both colors and produces a new color. * @param {Wick.Color} color - the color to add to this color * @returns {Wick.Color} the resulting color */ - - add(color) { var newColor = new Wick.Color(); newColor.r = this.r + color.r; @@ -46047,13 +46032,12 @@ Wick.Color = class { newColor.b = this.b + color.b; return newColor; } + /** * Multiplies the r, g, and b values of both colors to produce a new color. * @param {Wick.Color} color - the color to multiply with this color * @returns {Wick.Color} the resulting color */ - - multiply(n) { var newColor = new Wick.Color(); newColor.r = this.r * n; @@ -46061,18 +46045,16 @@ Wick.Color = class { newColor.b = this.b * n; return newColor; } + /** * Averages the r, g, and b values of two colors. * @param {Wick.Color} colorA - a color to average with another color (order does not matter) * @param {Wick.Color} colorB - a color to average with another color (order does not matter) * @returns {Wick.Color} The resulting averaged color. */ - - static average(colorA, colorB) { return colorA.multiply(0.5).add(colorB.multiply(0.5)); } - }; /* * Copyright 2020 WICKLETS LLC @@ -46104,30 +46086,28 @@ Wick.FileCache = class { static get FILE_LOCALFORAGE_KEY_PREFIX() { return 'filesrc_'; // This should never change. } + /** * Add a file to the cache. * @param {string} src - The file source * @param {string} uuid - The UUID of the file */ - - static addFile(src, uuid) { this._files[uuid] = { src: src - }; // Save asset to localforage + }; + // Save asset to localforage localforage.setItem(this.getLocalForageKeyForUUID(uuid), src).then(() => {}); } + /** * Get info for a file by its UUID. * @param {string} uuid - The UUID of the file * @returns {object} The file info */ - - static getFile(uuid) { var file = this._files[uuid]; - if (!file) { console.error('Asset with UUID ' + uuid + ' was not found in FileCache!'); return null; @@ -46135,24 +46115,23 @@ Wick.FileCache = class { return file; } } + /** * Removes a file from the FileCache with a given UUID. * @param {string} uuid - the UUID of the file to remove. */ - - static removeFile(uuid) { - delete this._files[uuid]; // Remove file from localforage + delete this._files[uuid]; + // Remove file from localforage localforage.removeItem(this.getLocalForageKeyForUUID(uuid)).then(() => {}); } + /** * Loads all files from local forage associated with a previously saved project, if possible. * @param {Wick.Project} project - the project that we want to load assets for. * @param {function} callback - called when the assets are done being loaded. */ - - static loadFilesFromLocalforage(project, callback) { Promise.all(project.getAssets().map(asset => { return localforage.getItem(this.getLocalForageKeyForUUID(asset.uuid)); @@ -46160,48 +46139,40 @@ Wick.FileCache = class { for (var i = 0; i < assets.length; i++) { this.addFile(assets[i], project.getAssets()[i].uuid); } - callback(); }); } + /** * On object containing all files in WickFileCache. * @returns {object} All the files in an object with the format: */ - - static getAllFiles() { var files = []; - for (var uuid in this._files) { files.push({ uuid: uuid, src: this._files[uuid].src }); } - return files; } + /** * Clear the cache. */ - - static clear() { this._files = {}; } - static clearLocalforage() { // Clear all files from localforage for (var uuid in this._files) { localforage.removeItem(this.getLocalForageKeyForUUID(uuid)).then(() => {}); } } - static getLocalForageKeyForUUID(uuid) { return this.FILE_LOCALFORAGE_KEY_PREFIX + uuid; } - }; Wick.FileCache._files = {}; /* @@ -46234,11 +46205,10 @@ Wick.History = class { static get VERBOSE() { return false; } + /** * An Enum of all types of state saves. */ - - static get StateType() { return { ALL_OBJECTS: 1, @@ -46246,57 +46216,48 @@ Wick.History = class { ONLY_VISIBLE_OBJECTS: 3 }; } + /** * Creates a new history object */ - - constructor() { this.reset(); this.lastHistoryPush = Date.now(); } + /** * Resets history in the editor. This is non-reversible. */ - - reset() { this._undoStack = []; this._redoStack = []; this._snapshots = {}; } + /** * Returns all objects that are currently referenced by the history. * @returns {Set} uuids of all objects currently referenced in the history. */ - - getObjectUUIDs() { let objects = new Set(); - for (let state of this._undoStack) { objects = new Set([...objects, ...state.objects]); } - for (let state of this._redoStack) { objects = new Set([...objects, ...state.objects]); } - return objects; } + /** * Push the current state of the ObjectCache to the undo stack. * @param {number} filter - the filter to choose which objects to serialize. See Wick.History.StateType * @param {string} actionName - Optional: Name of the action conducted to generate this state. If no name is presented, "Unknown Action" is presented in its place. */ - - pushState(filter, actionName) { this._redoStack = []; let now = Date.now(); - let state = this._generateState(filter); - let objects = new Set(state.map(obj => obj.uuid)); let stateObject = { state: this._generateState(filter), @@ -46305,103 +46266,84 @@ Wick.History = class { timeSinceLastPush: now - this.lastHistoryPush }; this.lastHistoryPush = now; - this._undoStack.push(stateObject); - this._undoStack = this._undoStack.slice(-64); // get the last 64 items in the undo stack } + /** * Pop the last state in the undo stack off and apply the new last state to the project. * @returns {boolean} True if the undo stack is non-empty, false otherwise */ - - popState() { if (this._undoStack.length <= 1) { return false; } - var lastState = this._undoStack.pop(); - this._redoStack.push(lastState); + var currentStateObject = this._undoStack[this._undoStack.length - 1]; - var currentStateObject = this._undoStack[this._undoStack.length - 1]; // 1.17.1 History update, pull actual state information out, aside from names. - + // 1.17.1 History update, pull actual state information out, aside from names. var currentState = currentStateObject; - if (currentStateObject.state) { currentState = currentStateObject.state; } - this._recoverState(currentState); - return true; } + /** * Recover a state that was undone. * @returns {boolean} True if the redo stack is non-empty, false otherwise */ - - recoverState() { if (this._redoStack.length === 0) { return false; } - var recoveredState = this._redoStack.pop().state; - this._undoStack.push(recoveredState); - this._recoverState(recoveredState); - return true; } + /** * * @param {string} name - the name of the snapshot * @param {number} filter - the filter to choose which objects to serialize. See Wick.History.StateType */ - - saveSnapshot(name, filter) { this._snapshots[name] = this._generateState(filter || Wick.History.StateType.ALL_OBJECTS_WITHOUT_PATHS); } + /** * Save a state to the list of snapshots to be recovered at any time. * @param {string} name - the name of the snapshot to recover */ - - loadSnapshot(name) { this._recoverState(this._snapshots[name]); } + /** * The number of states currently stored for undoing. * @type {number} */ - - get numUndoStates() { return this._undoStack.length; } + /** * The number of states currently stored for redoing. * @type {number} */ - - get numRedoStates() { return this._redoStack.length; - } // NOTE: State saving/recovery can be greatly optimized by only saving the state of the things that were actually changed. - + } + // NOTE: State saving/recovery can be greatly optimized by only saving the state of the things that were actually changed. _generateState(stateType) { var objects = []; - if (stateType === undefined) { stateType = Wick.History.StateType.ALL_OBJECTS; } - if (stateType === Wick.History.StateType.ALL_OBJECTS) { objects = this._getAllObjects(); } else if (stateType === Wick.History.StateType.ALL_OBJECTS_WITHOUT_PATHS) { @@ -46412,61 +46354,64 @@ Wick.History = class { console.error('Wick.History._generateState: A valid stateType is required.'); return; } - if (Wick.History.VERBOSE) { console.log('Wick.History._generateState: Serializing ' + objects.length + ' objects using mode=' + stateType); } - return objects.map(object => { // The object most likely was altered in some way, make sure those changes will be reflected in the autosave. object.needsAutosave = true; return object.serialize(); }); } - _recoverState(state) { state.forEach(objectData => { var object = Wick.ObjectCache.getObjectByUUID(objectData.uuid); object.deserialize(objectData); }); } - _getAllObjects() { var objects = Wick.ObjectCache.getActiveObjects(this.project); objects.push(this.project); return objects; - } // this is used for an optimization when snapshots are saved for preview playing. - + } + // this is used for an optimization when snapshots are saved for preview playing. _getAllObjectsWithoutPaths() { return this._getAllObjects().filter(object => { return !(object instanceof Wick.Path); }); } - _getVisibleObjects() { - var stateObjects = []; // the project itself (for focus, options, etc) + var stateObjects = []; - stateObjects.push(this.project); // the assets in the project + // the project itself (for focus, options, etc) + stateObjects.push(this.project); + // the assets in the project this.project.getAssets().forEach(asset => { stateObjects.push(asset); - }); // the focused clip + }); - stateObjects.push(this.project.focus); // the focused timeline + // the focused clip + stateObjects.push(this.project.focus); - stateObjects.push(this.project.focus.timeline); // the selection + // the focused timeline + stateObjects.push(this.project.focus.timeline); - stateObjects.push(this.project.selection); // layers on focused timeline + // the selection + stateObjects.push(this.project.selection); + // layers on focused timeline this.project.activeTimeline.layers.forEach(layer => { stateObjects.push(layer); - }); // frames on focused timeline + }); + // frames on focused timeline this.project.activeTimeline.frames.forEach(frame => { stateObjects.push(frame); - }); // objects+tweens on active frames + }); + // objects+tweens on active frames this.project.activeFrames.forEach(frame => { frame.paths.forEach(path => { stateObjects.push(path); @@ -46480,7 +46425,6 @@ Wick.History = class { }); return stateObjects; } - }; /* * Copyright 2020 WICKLETS LLC @@ -46500,6 +46444,7 @@ Wick.History = class { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + // NOTE: // This should probably not be global, and instead, each Wick.Project should own an ObjectCache. // It's too hard to test if there's a shared ObjectCache between many projects. @@ -46515,24 +46460,23 @@ WickObjectCache = class { this._objects = {}; this._objectsNeedAutosave = {}; } + /** * Add an object to the cache. * @param {Wick.Base} object - the object to add */ - - addObject(object) { this._objects[object.uuid] = object; + /*object.children.forEach(child => { this.addObject(child); });*/ } + /** * Remove an object from the cache. * @param {Wick.Base} object - the object to remove from the cache */ - - removeObject(object) { if (object.classname === 'Project') { object.destroy(); @@ -46541,37 +46485,32 @@ WickObjectCache = class { delete this._objects[object.uuid]; } + /** * Remove an object from the cache. * @param {string} uuid - uuid of the object to remove from the cache */ - - removeObjectByUUID(uuid) { delete this._objects[uuid]; } + /** * Remove all objects from the Object Cache. */ - - clear() { this._objects = {}; this._objectsNeedAutosave = {}; } + /** * Get an object by its UUID. * @returns {Wick.Base} */ - - getObjectByUUID(uuid) { if (!uuid) { console.error('ObjectCache: getObjectByUUID: uuid is required.'); } - var object = this._objects[uuid]; - if (!object) { console.error("Warning: object with uuid " + uuid + " was not found in the cache."); return null; @@ -46579,29 +46518,25 @@ WickObjectCache = class { return object; } } + /** * All objects in the cache. * @returns {Wick.Base[]} */ - - getAllObjects() { var allObjects = []; - for (var uuid in this._objects) { allObjects.push(this._objects[uuid]); } - return allObjects; } + /** * Remove all objects that are in the project, but are no longer linked to the root object. * This is basically a garbage collection function. This function attempts to keep objects * that are referenced in undo/redo. * @param {Wick.Project} project - the project to use to determine which objects have no references */ - - removeUnusedObjects(project) { var activeObjects = this.getActiveObjects(project); let uuids = activeObjects.map(obj => obj.uuid); @@ -46616,11 +46551,10 @@ WickObjectCache = class { } }); } + /** * Removes all objects with the temporary flag set to true. */ - - removeTemporaryObjects() { this.getAllObjects().forEach(obj => { if (obj.temporary) { @@ -46628,56 +46562,50 @@ WickObjectCache = class { } }); } + /** * Get all objects that are referenced in the given project. * @param {Wick.Project} project - the project to check if children are active in. * @returns {Wick.Base[]} the active objects. */ - - getActiveObjects(project) { // This does the same thing, but it's WAY faster. return project.getChildrenRecursive().map(object => { return this.getObjectByUUID(object.uuid); }); } + /** * Saves an object to be autosaved upon the next auto save. * @param {Wick.Base} object object to be saved. */ - - markObjectToBeAutosaved(object) { this._objectsNeedAutosave[object.uuid] = true; } + /** * Removes a given object from the list of objects that must be autosaved. * @param {Wick.Base} object - the object to remove from the list of objects to be autosaved. */ - - clearObjectToBeAutosaved(object) { delete this._objectsNeedAutosave[object.uuid]; } + /** * Returns true if a given object is marked to be autosaved during the next autosave. * @param {Wick.Base} object - the object to check for autosave */ - - objectNeedsAutosave(object) { return Wick.ObjectCache._objectsNeedAutosave[object.uuid]; } + /** * Returns an array of objects that currently need to be autosaved. * @returns {Wick.Base[]} The objects that are marked to be autosaved. */ - - getObjectsNeedAutosaved() { return Object.keys(this._objectsNeedAutosave).map(uuid => this.getObjectByUUID(uuid)); } - }; Wick.ObjectCache = new WickObjectCache(); /* @@ -46719,11 +46647,10 @@ Wick.Transformation = class { this.rotation = args.rotation === undefined ? 0 : args.rotation; this.opacity = args.opacity === undefined ? 1 : args.opacity; } + /** * An object containing the values of this transformation. */ - - get values() { return { x: this.x, @@ -46734,16 +46661,14 @@ Wick.Transformation = class { opacity: this.opacity }; } + /** * Creates a copy of this transformation. * @returns {Wick.Transformation} the copied transformation. */ - - copy() { return new Wick.Transformation(this.values); } - }; /* * Copyright 2020 WICKLETS LLC @@ -46763,6 +46688,7 @@ Wick.Transformation = class { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.ToolSettings = class { static get DEFAULT_SETTINGS() { return [{ @@ -46856,41 +46782,37 @@ Wick.ToolSettings = class { options: ['none', 'behind', 'inside'] }]; } + /** * Create a new ToolSettings object. */ - - constructor() { this._settings = {}; - this._onSettingsChangedCallback = () => {}; - this.resetAllSettings(); this.loadSettingsFromLocalstorage(); } + /** * Returns the appropriate key to use to store a tool setting by name. * @param {String} settingName name of tool setting. * @returns {String} Key to be used. */ - - getStorageKey(settingName) { return "WICK.TOOLSETTINGS." + settingName; } + /** * Creates the tool settings at the start of the editor. Will open with previously used settings if they exist. */ - - createSetting(args) { if (!args) console.error('createSetting: args is required'); if (!args.name) console.error('createSetting: args.name is required'); if (args.default === undefined) console.error('createSetting: args.default is required'); let name = args.name; - let type = args.type; // Create a default setting to start. + let type = args.type; + // Create a default setting to start. this._settings[args.name] = { type: args.type, name: args.name, @@ -46902,68 +46824,57 @@ Wick.ToolSettings = class { options: args.options }; } + /** * Update a value in the settings. * @param {string} name - The name of the setting to update. * @param {string|number|Color} value - The value of the setting to change to. */ - - setSetting(name, value) { var setting = this._settings[name]; - if (!setting) return; // Check to make sure there's no type mismatch + if (!setting) return; + // Check to make sure there's no type mismatch if (typeof value !== typeof setting.value) { console.warn('Warning: Wick.ToolSettings: Type mismatch while setting ' + name); console.warn(value); return; } - var min = setting.min; - if (min !== undefined) { value = Math.max(min, value); } - var max = setting.max; - if (max !== undefined) { value = Math.min(max, value); } - setting.value = value; - this._fireOnSettingsChanged(name, value); - if (setting.type === 'color') { localforage.setItem(this.getStorageKey(name), value.rgba); } else { localforage.setItem(this.getStorageKey(name), value); } } + /** * Retrieve a value in the settings. * @param {string} name - The name of the setting to retrieve. */ - - getSetting(name) { var setting = this._settings[name]; - if (!setting) { console.error("ToolSettings.getSetting: invalid setting: " + name); return; } - return setting.value; } + /** * Returns an object with the setting restrictions for a provided setting. * @param {String} name name of tool setting * @returns {Object} an object containing the values min, max, step and options where appropriate. */ - - getSettingRestrictions(name) { var setting = this._settings[name]; if (!setting) console.error("ToolSettings.getSettingRestrictions: invalid setting: " + name); @@ -46974,44 +46885,38 @@ Wick.ToolSettings = class { options: setting.options }; } + /** * Returns an array containing all settings with all information. * @returns {Object[]} Array of settings objects. */ - - getAllSettings() { var allSettings = []; - for (var name in this._settings) { allSettings.push(this._settings[name]); } - return allSettings; } + /** * Receives a call back that will be provided the name and value of the setting that was changed. */ - - onSettingsChanged(callback) { this._onSettingsChangedCallback = callback; } + /** * Reset settings to the deafults. */ - - resetAllSettings() { Wick.ToolSettings.DEFAULT_SETTINGS.forEach(setting => { this.createSetting(setting); }); } + /** * Load settings from localstorage if they exist. */ - - loadSettingsFromLocalstorage() { Wick.ToolSettings.DEFAULT_SETTINGS.forEach(setting => { // Get stored tool setting if it exists. @@ -47031,11 +46936,9 @@ Wick.ToolSettings = class { }); }); } - _fireOnSettingsChanged(name, value) { this._onSettingsChangedCallback(name, value); } - }; /* * Copyright 2020 WICKLETS LLC @@ -47055,40 +46958,38 @@ Wick.ToolSettings = class { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + GlobalAPI = class { /** * Defines all api members such as functions and properties. * @type {string[]} */ static get apiMemberNames() { - return ['stop', 'play', 'gotoAndStop', 'gotoAndPlay', 'gotoNextFrame', 'gotoPrevFrame', // These are currently disabled, they are very slow for some reason. + return ['stop', 'play', 'gotoAndStop', 'gotoAndPlay', 'gotoNextFrame', 'gotoPrevFrame', + // These are currently disabled, they are very slow for some reason. // They are currently hacked in inside Tickable._runFunction //'project','root','parent','parentObject', 'isMouseDown', 'mouseX', 'mouseY', 'mouseMoveX', 'mouseMoveY', 'key', 'keys', 'isKeyDown', 'keyIsDown', 'isKeyJustPressed', 'keyIsJustPressed', 'random', 'playSound', 'stopAllSounds', 'onEvent', 'hideCursor', 'showCursor', 'hitTestOptions']; } + /** * @param {object} scriptOwner The tickable object which owns the script being evaluated. */ - - constructor(scriptOwner) { this.scriptOwner = scriptOwner; } + /** * Returns a list of api members bound to the script owner. * @returns {object[]} Array of functions, properties, and api members. */ - - get apiMembers() { var members = []; GlobalAPI.apiMemberNames.forEach(name => { var fn = this[name]; - if (fn instanceof Function) { fn = fn.bind(this); } - members.push({ name: name, fn: fn @@ -47096,69 +46997,60 @@ GlobalAPI = class { }); return members; } + /** * Stops the timeline of the object's parent clip. */ - - stop() { this.scriptOwner.parentClip.stop(); } + /** * Plays the timeline of the object's parent clip. */ - - play() { this.scriptOwner.parentClip.play(); } + /** * Moves the plahead of the parent clip to a frame and stops the timeline of that parent clip. * @param {string | number} frame Frame name or number to move playhead to. */ - - gotoAndStop(frame) { this.scriptOwner.parentClip.gotoAndStop(frame); } + /** * Moves the plahead of the parent clip to a frame and plays the timeline of that parent clip. * @param {string | number} frame Frame name or number to move playhead to. */ - - gotoAndPlay(frame) { this.scriptOwner.parentClip.gotoAndPlay(frame); } + /** * Moves the playhead of the parent clip of the object to the next frame. */ - - gotoNextFrame() { this.scriptOwner.parentClip.gotoNextFrame(); } + /** * Moves the playhead of the parent clip of this object to the previous frame. */ - - gotoPrevFrame() { this.scriptOwner.parentClip.gotoPrevFrame(); } - hitTestOptions(options) { this.scriptOwner.project.hitTestOptions = options; } + /** * Returns an object representing the project with properties such as width, height, framerate, background color, and name. * @returns {object} Project object. */ - - get project() { var project = this.scriptOwner.project && this.scriptOwner.project.root; - if (project) { // Attach some aliases to the project settings project.width = this.scriptOwner.project.width; @@ -47168,219 +47060,197 @@ GlobalAPI = class { project.name = this.scriptOwner.project.name; project.hitTestOptions = this.scriptOwner.project.hitTestOptions; } - return project; } + /** * @deprecated * Legacy item which returns the project. Use 'project' instead. */ - - get root() { return this.project; } + /** * Returns a reference to the current object's parent. * @returns Current object's parent. */ - - get parent() { return this.scriptOwner.parentClip; } + /** * @deprecated * Legacy item which returns the parent clip. Use 'parent' instead. */ - - get parentObject() { return this.scriptOwner.parentClip; } + /** * Returns the last key pressed down. * @returns {string | null} Returns null if no key has been pressed yet. */ - - get key() { if (!this.scriptOwner.project) return null; return this.scriptOwner.project.currentKey; } + /** * Returns a list of all keys currently pressed down. * @returns {string[]} All keys represented as strings. If no keys are pressed, an empty array is returned. */ - - get keys() { if (!this.scriptOwner.project) return null; return this.scriptOwner.project.keysDown; } + /** * Returns true if the given key is currently down. * @param {string} key * @returns {bool} */ - - isKeyDown(key) { if (!this.scriptOwner.project) return null; return this.scriptOwner.project.isKeyDown(key); } + /** * @deprecated * Legacy item, use 'isKeyDown' instead. */ - - keyIsDown(key) { return this.isKeyDown(key.toLowerCase()); } + /** * Returns true if the given key was just pressed within the last tick. * @param {string} key * @returns {bool} */ - - isKeyJustPressed(key) { if (!this.scriptOwner.project) return null; return this.scriptOwner.project.isKeyJustPressed(key); } + /** * @deprecated * Legacy item, use 'isKeyJustPressed' instead. */ - - keyIsJustPressed(key) { return this.keyIsJustPressed(key.toLowerCase()); } + /** * Returns true if the mouse is currently held down. * @returns {bool | null} Returns null if the object does not have a project. */ - - isMouseDown() { if (!this.scriptOwner.project) return null; return this.scriptOwner.project.isMouseDown; } + /** * Returns the current x position of the mouse in relation to the canvas. * @returns {number} */ - - get mouseX() { if (!this.scriptOwner.project) return null; return this.scriptOwner.project.mousePosition.x; } + /** * Returns the current y position of the mouse in relation to the canvas. * @returns {number} */ - - get mouseY() { if (!this.scriptOwner.project) return null; return this.scriptOwner.project.mousePosition.y; } + /** * Returns the amount the mouse moved in the last tick on the x axis. * @returns {number} */ - - get mouseMoveX() { if (!this.scriptOwner.project) return null; return this.scriptOwner.project.mouseMove.x; } + /** * Returns the amount the mouse moved in the last tick on the y axis. * @returns {number} */ - - get mouseMoveY() { if (!this.scriptOwner.project) return null; return this.scriptOwner.project.mouseMove.y; } + /** * Returns a new random object. * @returns {GlobalAPI.Random} */ - - get random() { return new GlobalAPI.Random(); } + /** * Plays a sound which is currently in the asset library. * @param {string} name - name of the sound asset in the library. * @param {Object} options - options for the sound. See Wick.SoundAsset.play * @returns {object} object representing the sound which was played. */ - - playSound(assetName, options) { if (!this.scriptOwner.project) return null; return this.scriptOwner.project.playSound(assetName, options); } + /** * Stops sound(s) currently playing. * @param {string} assetName - The name of the SoundAsset to stop. * @param {number} id - (optional) The ID of the sound to stop. Returned by playSound. If an ID is not given, all instances of the given sound asset will be stopped. */ - - stopSound(assetName, id) { if (!this.scriptOwner.project) return null; return this.scriptOwner.project.stopSound(assetName, id); } + /** * Stops all currently playing sounds. */ - - stopAllSounds() { if (!this.scriptOwner.project) return null; this.scriptOwner.project.stopAllSounds(); } + /** * Attach a function to an event with a given name. * @param {string} name - the name of the event to attach the function to * @param {function} fn - the function to attach to the event */ - - onEvent(name, fn) { this.scriptOwner.onEvent(name, fn); } + /** * Hide the cursor while the project is running. */ - - hideCursor() { if (!this.scriptOwner.project) return null; this.scriptOwner.project.hideCursor = true; } + /** * Don't hide the cursor while the project is running. */ - - showCursor() { if (!this.scriptOwner.project) return null; this.scriptOwner.project.hideCursor = false; } - }; GlobalAPI.Random = class { constructor() {} + /** * Returns a random integer (whole number) between two given numbers, 0 and a given number, or 0 and 1. The random number is inclusive of the maximum range. * @param {number} min The minimum of the returned integer, or the maximum of the returned number if it is the only argument. @@ -47388,8 +47258,6 @@ GlobalAPI.Random = class { * @returns {number} A random number between num1 and num2, 0 and num1, or 0 and 1. Will return 0 if max is greater than min. * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random */ - - integer(min, max) { if (typeof min === 'undefined' && typeof max === 'undefined') { min = 0; @@ -47398,11 +47266,12 @@ GlobalAPI.Random = class { max = Math.ceil(min); min = 0; } + if (max < min) return 0; - if (max < min) return 0; // The maximum is inclusive and the minimum is inclusive - + // The maximum is inclusive and the minimum is inclusive return Math.floor(Math.random() * (max - min + 1) + min); } + /** * Returns a random floating point (decimal) number between two given numbers, 0 and a given number, or 0 and 1. * @param {number} num1 The minimum of the returned number, or the maximum of the returned number if it is the only argument. @@ -47410,8 +47279,6 @@ GlobalAPI.Random = class { * @returns {number} A random number between num1 and num2, 0 and num1, or 0 and 1. * https://stackoverflow.com/questions/4959975/generate-random-number-between-two-numbers-in-javascript */ - - float(num1, num2) { if (typeof num1 !== "undefined" && typeof num2 !== "undefined") { return Math.random() * (num2 - num1) + num1; @@ -47421,19 +47288,17 @@ GlobalAPI.Random = class { return Math.random(); } } + /** * Returns a random item from an array of items. * @param {array} An array of objects. * @returns {object | null} A random item contained in the array. Returns null if the given array has no items. * https://stackoverflow.com/questions/4550505/getting-a-random-value-from-a-javascript-array */ - - choice(array) { if (array.length <= 0) return null; return array[Math.floor(Math.random() * array.length)]; } - }; /* * Copyright 2020 WICKLETS LLC @@ -47464,22 +47329,26 @@ BuiltinAssets = class { width: 720, height: 480 }; - var defaultCrosshairSize = 75; // Vcam outline (hidden when project plays) + var defaultCrosshairSize = 75; - var vcamBorderPaths = [// Cam border + // Vcam outline (hidden when project plays) + var vcamBorderPaths = [ + // Cam border new paper.Path.Rectangle({ from: new paper.Point(-defaultVcamWH.width / 2, -defaultVcamWH.height / 2), to: new paper.Point(defaultVcamWH.width / 2, defaultVcamWH.height / 2), strokeWidth: 1, strokeColor: '#000', fillColor: 'rgba(74,144,226,0.19)' - }), // Cam center crosshair (vertical line) + }), + // Cam center crosshair (vertical line) new paper.Path.Line({ from: new paper.Point(0, -defaultCrosshairSize / 2), to: new paper.Point(0, defaultCrosshairSize / 2), strokeWidth: 1, strokeColor: '#000' - }), // Cam center crosshair (horizontal line) + }), + // Cam center crosshair (horizontal line) new paper.Path.Line({ from: new paper.Point(-defaultCrosshairSize / 2, 0), to: new paper.Point(defaultCrosshairSize / 2, 0), @@ -47490,31 +47359,36 @@ BuiltinAssets = class { vcam.activeFrame.addPath(new Wick.Path({ path: vcamPath })); - }); // Vcam black borders (only visible when project is playing and showBlackBorders is set to true) + }); + // Vcam black borders (only visible when project is playing and showBlackBorders is set to true) var borderSize = 10000; - var blackBorderPaths = [// Black border top + var blackBorderPaths = [ + // Black border top new paper.Path.Rectangle({ from: new paper.Point(-borderSize, -borderSize), to: new paper.Point(borderSize, -defaultVcamWH.height / 2), strokeWidth: 1, strokeColor: '#000', fillColor: '#000' - }), // Black border bottom + }), + // Black border bottom new paper.Path.Rectangle({ from: new paper.Point(-borderSize, defaultVcamWH.height / 2), to: new paper.Point(borderSize, borderSize), strokeWidth: 1, strokeColor: '#000', fillColor: '#000' - }), // Black border left + }), + // Black border left new paper.Path.Rectangle({ from: new paper.Point(-borderSize, -borderSize), to: new paper.Point(-defaultVcamWH.width / 2, borderSize), strokeWidth: 1, strokeColor: '#000', fillColor: '#000' - }), // Black border right + }), + // Black border right new paper.Path.Rectangle({ from: new paper.Point(defaultVcamWH.width / 2, -borderSize), to: new paper.Point(borderSize, borderSize), @@ -47529,12 +47403,14 @@ BuiltinAssets = class { vcam.activeLayer.getFrameAtPlayheadPosition(2).addPath(new Wick.Path({ path: vcamPath })); - }); // Blank frame + }); + // Blank frame vcam.activeLayer.addFrame(new Wick.Frame({ start: 3 - })); // Build script + })); + // Build script var vcamScript = ""; vcamScript += "// Wick VCam Beta v0.02\n"; vcamScript += "\n"; @@ -47568,7 +47444,6 @@ BuiltinAssets = class { vcam.removeScript('default'); return vcam; } - }; Wick.BuiltinAssets = new BuiltinAssets(); /* @@ -47589,30 +47464,34 @@ Wick.BuiltinAssets = new BuiltinAssets(); * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.ExportUtils = class { // https://stackoverflow.com/questions/12168909/blob-from-dataurl static dataURItoBlob(dataURI) { // convert base64 to raw binary data held in a string // doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this - var byteString = atob(dataURI.split(',')[1]); // separate out the mime component + var byteString = atob(dataURI.split(',')[1]); - var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; // write the bytes of the string to an ArrayBuffer + // separate out the mime component + var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; - var ab = new ArrayBuffer(byteString.length); // create a view into the buffer + // write the bytes of the string to an ArrayBuffer + var ab = new ArrayBuffer(byteString.length); - var ia = new Uint8Array(ab); // set the bytes of the buffer to the correct values + // create a view into the buffer + var ia = new Uint8Array(ab); + // set the bytes of the buffer to the correct values for (var i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); - } // write the ArrayBuffer to a blob, and you're done - + } + // write the ArrayBuffer to a blob, and you're done var blob = new Blob([ab], { type: mimeString }); return blob; } - }; /* * Copyright 2020 WICKLETS LLC @@ -47632,6 +47511,7 @@ Wick.ExportUtils = class { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.AudioTrack = class { /** * @type {Wick.Project} @@ -47639,46 +47519,40 @@ Wick.AudioTrack = class { get project() { return this._project; } - set project(project) { this._project = project; } + /** * Create a new AudioTrack * @param {Wick.Project} project - the project to use audio from */ - - constructor(project) { this._project = project; } + /** * Generate an AudioBuffer of all the project's sounds as one audio track. * Can take sound information from a generated sequence. * @param {Object} args - callback, onProgress, soundInfo */ - - toAudioBuffer(args) { if (!args) args = {}; if (!args.callback) args.callback = () => {}; if (!args.onProgress) args.onProgress = (frame, maxFrames) => {}; - let genBuffer = audioInfo => { if (!audioInfo) args.callback(null); - if (audioInfo.length === 0) { // No audio in the project, no AudioBuffer to create args.callback(null); return; } - Wick.AudioTrack.generateProjectAudioBuffer(audioInfo, audioArraybuffer => { args.callback(audioArraybuffer); }, args.onProgress); - }; // If audio information is passed in from a previous render, use that. Otherwise, render it again. - + }; + // If audio information is passed in from a previous render, use that. Otherwise, render it again. if (args.soundInfo) { genBuffer(args.soundInfo); } else { @@ -47688,19 +47562,17 @@ Wick.AudioTrack = class { }); } } + /** * Create an AudioBuffer from given sounds. * @param {object[]} projectAudioInfo - infor generated on sounds played in the project. * @param {Function} callback - callback to recieve the generated AudioBuffer * @param {Function} onProgress(message, progress) - A function which receive a message. */ - - static generateProjectAudioBuffer(projectAudioInfo, callback, onProgress) { window.AudioContext = window.AudioContext || window.webkitAudioContext; var ctx = new AudioContext(); let audiobuffers = []; - let mergeAudio = () => { onProgress && onProgress("Merging Audio"); audiobuffers.sort((a, b) => { @@ -47714,12 +47586,10 @@ Wick.AudioTrack = class { }); callback(mergedAudioBuffer); }; - for (let i = 0; i < projectAudioInfo.length; i++) { let audioInfo = projectAudioInfo[i]; this.base64ToAudioBuffer(audioInfo.src, ctx, audiobuffer => { let offset = audioInfo.offset || 0; // Milliseconds to offset. - let offsetSeconds = offset / 1000; // Adjust to seconds. let startSeconds = audioInfo.start / 1000; @@ -47732,67 +47602,64 @@ Wick.AudioTrack = class { let delayedAudiobuffer = this.addStartDelayToAudioBuffer(volumeAdjustedAudioBuffer, startSeconds, ctx); onProgress && onProgress("Creating Audio " + (i + 1) + "/" + projectAudioInfo.length, (i + 1) / projectAudioInfo.length); audiobuffers.push(delayedAudiobuffer); - if (audiobuffers.length >= projectAudioInfo.length) { mergeAudio(); } }); } } + /* * Merges multiple audiobuffers into a single audiobuffer. * @param {AudioBuffer[]} buffers - the AudioBuffers to merge together * @param {AudioContext} ac - An AudioContext instance */ - - static mergeBuffers(buffers, ac, onProgress) { // original function from: // https://github.com/meandavejustice/merge-audio-buffers/blob/master/index.js + var maxChannels = 0; - var maxDuration = 0; // Send back an empty buffer if no information was sent in. + var maxDuration = 0; + // Send back an empty buffer if no information was sent in. if (!buffers || buffers && buffers.length === 0) { return ac.createBuffer(2, 1000, 48000); - } // Review the incoming audio to determine output buffer size. - + } + // Review the incoming audio to determine output buffer size. for (let i = 0; i < buffers.length; i++) { onProgress("Reviewing Audio " + (i + 1) + "/" + buffers.length, i + 1 + "/" + buffers.length); - if (buffers[i].numberOfChannels > maxChannels) { maxChannels = buffers[i].numberOfChannels; } - if (buffers[i].duration > maxDuration) { maxDuration = buffers[i].duration; } - } // Create new output buffer. - + } + // Create new output buffer. var out = ac.createBuffer(maxChannels, ac.sampleRate * maxDuration, ac.sampleRate); - for (var i = 0; i < buffers.length; i++) { - onProgress("Merging Audio " + (i + 1) + "/" + buffers.length, i + 1 + "/" + buffers.length); // Go through each channel of the new audio source and copy that data into the output buffer. + onProgress("Merging Audio " + (i + 1) + "/" + buffers.length, i + 1 + "/" + buffers.length); + // Go through each channel of the new audio source and copy that data into the output buffer. for (var srcChannel = 0; srcChannel < buffers[i].numberOfChannels; srcChannel++) { var outt = out.getChannelData(srcChannel); var inn = buffers[i].getChannelData(srcChannel); - for (let j = 0; j < inn.length; j++) { - let val = inn[j]; // Some sounds may have corrupted data... don't copy that over. + let val = inn[j]; + // Some sounds may have corrupted data... don't copy that over. if (val) { outt[j] += val; } } - out.getChannelData(srcChannel).set(outt, 0); } } - return out; } + /** * Offsets an audio buffer by a number of seconds. * @param {audioBuffer} originalBuffer - Buffer to offset. @@ -47800,41 +47667,37 @@ Wick.AudioTrack = class { * @param {AudioContext} ctx - Context to use. * @returns {audioBuffer} - A copy of the audio buffer, offset by the provided number of seconds. */ - - static offsetAudioBuffer(originalBuffer, offsetSeconds, ctx) { // Create a blank buffer with the length of the original buffer. var offsetBuffer = ctx.createBuffer(originalBuffer.numberOfChannels, originalBuffer.length, ctx.sampleRate); let copyto = 0; let copyfrom = 0; - if (offsetSeconds < 0) { copyto = -1 * offsetSeconds * ctx.sampleRate; } else { copyfrom = offsetSeconds * ctx.sampleRate; - } // Copy buffer information. - + } + // Copy buffer information. for (var srcChannel = 0; srcChannel < offsetBuffer.numberOfChannels; srcChannel++) { // Retrieve sample data... var offsetBufferChannelData = offsetBuffer.getChannelData(srcChannel); - var originalBufferChannelData = originalBuffer.getChannelData(srcChannel); // Copy samples from the original buffer to the adjusted buffer, adjusting for the number of seconds to offset. + var originalBufferChannelData = originalBuffer.getChannelData(srcChannel); + // Copy samples from the original buffer to the adjusted buffer, adjusting for the number of seconds to offset. for (var i = 0; i < offsetBufferChannelData.length; i++) { if (i + copyfrom > originalBufferChannelData.length) { break; } else if (i + copyto > offsetBufferChannelData.length) { break; } - offsetBufferChannelData[i + copyto] = originalBufferChannelData[i + copyfrom]; } - offsetBuffer.getChannelData(srcChannel).set(offsetBufferChannelData, 0); } - return offsetBuffer; } + /** * Crops an AudioBuffer to a given length. * @param {AudioBuffer} originalBuffer - the buffer to crop @@ -47842,26 +47705,25 @@ Wick.AudioTrack = class { * @param {AudioContext} ctx - An AudioContext instance * @returns {AudioBuffer} - The a copy of the buffer, cropped to the specified length. */ - - static cropAudioBuffer(originalBuffer, lengthSeconds, ctx) { // Create a blank buffer with a length of the crop amount - var croppedBuffer = ctx.createBuffer(originalBuffer.numberOfChannels, ctx.sampleRate * lengthSeconds, ctx.sampleRate); // Copy data from the original buffer into the cropped buffer + var croppedBuffer = ctx.createBuffer(originalBuffer.numberOfChannels, ctx.sampleRate * lengthSeconds, ctx.sampleRate); + // Copy data from the original buffer into the cropped buffer for (var srcChannel = 0; srcChannel < croppedBuffer.numberOfChannels; srcChannel++) { // Retrieve sample data... var croppedBufferChannelData = croppedBuffer.getChannelData(srcChannel); - var originalBufferChannelData = originalBuffer.getChannelData(srcChannel); // Copy samples from the original buffer to the cropped buffer + var originalBufferChannelData = originalBuffer.getChannelData(srcChannel); + // Copy samples from the original buffer to the cropped buffer for (var i = 0; i < croppedBufferChannelData.length; i++) { croppedBufferChannelData[i] = originalBufferChannelData[i]; } - croppedBuffer.getChannelData(srcChannel).set(croppedBufferChannelData, 0); } - return croppedBuffer; } + /** * Adjusts the volume of an audio buffer. * @param {*} originalBuffer - The original buffer to adjust. @@ -47869,48 +47731,46 @@ Wick.AudioTrack = class { * @param {*} ctx - The audio context to use for buffer generation. * @returns {AudioBuffer} - Adjusted audio buffer with new volume. */ - - static adjustBufferVolume(originalBuffer, volume, ctx) { // Create a blank buffer with the length of the original buffer. - var adjustedBuffer = ctx.createBuffer(originalBuffer.numberOfChannels, originalBuffer.length, ctx.sampleRate); // Volume should be at least 0. + var adjustedBuffer = ctx.createBuffer(originalBuffer.numberOfChannels, originalBuffer.length, ctx.sampleRate); + // Volume should be at least 0. volume = Math.max(volume, 0); - for (var srcChannel = 0; srcChannel < adjustedBuffer.numberOfChannels; srcChannel++) { // Retrieve sample data... var adjustedBufferChannelData = adjustedBuffer.getChannelData(srcChannel); - var originalBufferChannelData = originalBuffer.getChannelData(srcChannel); // Copy samples from the original buffer to the adjusted buffer, adjusting for volume. + var originalBufferChannelData = originalBuffer.getChannelData(srcChannel); + // Copy samples from the original buffer to the adjusted buffer, adjusting for volume. for (var i = 0; i < adjustedBufferChannelData.length; i++) { adjustedBufferChannelData[i] = originalBufferChannelData[i] * volume; } - adjustedBuffer.getChannelData(srcChannel).set(adjustedBufferChannelData, 0); } - return adjustedBuffer; } + /** * Adds silence to the beginning of an AudioBuffer with a given length. * @param {AudioBuffer} originalBuffer - the buffer to pad with silence * @param {number} delaySeconds - the amount of time, in seconds, to delay the sound * @param {AudioContext} ctx - An AudioContext instance */ - - static addStartDelayToAudioBuffer(originalBuffer, delaySeconds, ctx) { // Create buffer with a length equal to the original buffer's length plus the requested delay + let lengthOfDelay = ctx.sampleRate * delaySeconds; let lengthOfOriginalSound = ctx.sampleRate * originalBuffer.duration; - var delayedBuffer = ctx.createBuffer(originalBuffer.numberOfChannels, lengthOfDelay + lengthOfOriginalSound, ctx.sampleRate); // For each channel in the audiobuffer... + var delayedBuffer = ctx.createBuffer(originalBuffer.numberOfChannels, lengthOfDelay + lengthOfOriginalSound, ctx.sampleRate); + // For each channel in the audiobuffer... for (var srcChannel = 0; srcChannel < originalBuffer.numberOfChannels; srcChannel++) { // Retrieve sample data... - var originalBufferChannelData = originalBuffer.getChannelData(srcChannel); // Copy samples from the original buffer to the delayed buffer with an offset equal to the delay + var originalBufferChannelData = originalBuffer.getChannelData(srcChannel); + // Copy samples from the original buffer to the delayed buffer with an offset equal to the delay var delayOffset = ctx.sampleRate * delaySeconds; - try { // Copy in the data from the original buffer into the delayed buffer, starting at the delayed position. delayedBuffer.getChannelData(srcChannel).set(originalBufferChannelData, delayOffset); @@ -47919,17 +47779,15 @@ Wick.AudioTrack = class { console.error("A sound was not added to the project."); } } - return delayedBuffer; } + /** * Convert a base64 string of an audio file into an AudioBuffer. * @param {string} base64 - a base64 dataURI of an audio file. * @param {AudioContext} ctx - an AudioContext instance. * @param {Function} callback - callback to recieve the generated AudioBuffer */ - - static base64ToAudioBuffer(base64, ctx, callback) { let base64DataOnly = base64.split(',')[1]; let arraybuffer = Base64ArrayBuffer.decode(base64DataOnly); @@ -47940,7 +47798,6 @@ Wick.AudioTrack = class { console.log(e); }); } - }; /* * Copyright 2020 WICKLETS LLC @@ -47972,21 +47829,19 @@ Wick.AutoSave = class { static get AUTOSAVES_LIST_KEY() { return 'autosaveList'; } + /** * The prefix to use for keys to save project autosave data. * @type {string} */ - - static get AUTOSAVE_DATA_PREFIX() { return 'autosave_'; } + /** * Saves a given project to localforage. * @param {Wick.Project} project - the project to store in the AutoSave system. */ - - static save(project, callback) { if (Wick.AutoSave.ENABLE_PERF_TIMERS) console.time('serialize step'); var autosaveData = this.generateAutosaveData(project); @@ -47999,13 +47854,12 @@ Wick.AutoSave = class { }); }); } + /** * Loads a given project from localforage. * @param {string} uuid - the UUID of the project to load from the AutoSave system. * @param {function} callback */ - - static load(uuid, callback) { this.readAutosaveData(uuid, autosaveData => { this.generateProjectFromAutosaveData(autosaveData, project => { @@ -48013,13 +47867,12 @@ Wick.AutoSave = class { }); }); } + /** * Deletes a project with a given UUID in the autosaves. * @param {string} uuid - uuid of project ot delete. * @param {function} callback */ - - static delete(uuid, callback) { this.removeAutosaveFromList(uuid, () => { this.deleteAutosaveData(uuid, () => { @@ -48027,12 +47880,11 @@ Wick.AutoSave = class { }); }); } + /** * Generates an object that is writable to localforage from a project. * @param {Wick.Project} project - The project to generate data for. */ - - static generateAutosaveData(project) { if (Wick.AutoSave.ENABLE_PERF_TIMERS) console.time('generate objects list'); var objects = Wick.ObjectCache.getActiveObjects(project); @@ -48050,32 +47902,32 @@ Wick.AutoSave = class { lastModified: lastModified }; } + /** * Creates a project from data loaded from the autosave system * @param {object} autosaveData - An autosave data object, use generateAutosaveData/readAutosaveData to get this object */ - - static generateProjectFromAutosaveData(autosaveData, callback) { // Deserialize all objects in the project so they are added to the ObjectCache autosaveData.objectsData.forEach(objectData => { var object = Wick.Base.fromData(objectData); - }); // Deserialize the project itself + }); - var project = Wick.Base.fromData(autosaveData.projectData); // Load source files for assets from localforage + // Deserialize the project itself + var project = Wick.Base.fromData(autosaveData.projectData); + // Load source files for assets from localforage Wick.FileCache.loadFilesFromLocalforage(project, () => { project.loadAssets(() => { callback(project); }); }); } + /** * Adds autosaved project data to the list of autosaved projects. * @param {Object} projectData - */ - - static addAutosaveToList(autosaveData, callback) { this.getAutosavesList(list => { list.push({ @@ -48087,12 +47939,11 @@ Wick.AutoSave = class { }); }); } + /** * Removes autosaved project data to the list of autosaved projects. * @param {string} uuid - */ - - static removeAutosaveFromList(uuid, callback) { this.getAutosavesList(list => { list = list.filter(item => { @@ -48103,72 +47954,66 @@ Wick.AutoSave = class { }); }); } + /** * Get the list of autosaved projects currently in the AutoSave system. * @param {function} callback - function to be passed object containing all autosaved projects. */ - - static getAutosavesList(callback) { localforage.getItem(this.AUTOSAVES_LIST_KEY).then(result => { - var projectList = result || []; // Sort by lastModified + var projectList = result || []; + // Sort by lastModified projectList.sort((a, b) => { return b.lastModified - a.lastModified; }); callback(projectList); }); } + /** * Updates the list of autosaved projects currently in the AutoSave system. * @param {Object} autosaveList - the list of projects * @param {function} callback - called when saving is finished */ - - static updateAutosavesList(autosaveList, callback) { localforage.setItem(this.AUTOSAVES_LIST_KEY, autosaveList).then(result => { callback(); }); } + /** * Save project data into the autosave system. * @param {Object} autosaveData - Autosave data of a project, use generateAutosaveData to create this object */ - - static writeAutosaveData(autosaveData, callback) { localforage.setItem(this.AUTOSAVE_DATA_PREFIX + autosaveData.projectData.uuid, autosaveData).then(() => { callback(); }); } + /** * Load project data from the autosave system. * @param {string} uuid - the UUID of the project to load */ - - static readAutosaveData(uuid, callback) { localforage.getItem(this.AUTOSAVE_DATA_PREFIX + uuid).then(result => { if (!result) { console.error('Could not load autosaveData for project: ' + uuid); } - callback(result); }); } + /** * Deletes project data from the autosave system. * @param {string} uuid - the UUID of the project to delete */ - - static deleteAutosaveData(uuid, callback) { localforage.removeItem(this.AUTOSAVE_DATA_PREFIX + uuid).then(() => { callback(); }); } - }; Wick.AutoSave.ENABLE_PERF_TIMERS = false; /* @@ -48217,78 +48062,66 @@ Wick.WickFile = class { } }; } + /** * Create a project from a wick file. * @param {File} wickFile - Wick file containing project data. * @param {function} callback - Function called when the project is created. * @param {string} format - The format to return. Can be 'blob' or 'base64'. */ - - static fromWickFile(wickFile, callback, format) { if (!format) { format = 'blob'; } - if (format !== 'blob' && format !== 'base64') { console.error('WickFile.toWickFile: invalid format: ' + format); return; } - var zip = new JSZip(); zip.loadAsync(wickFile, { base64: format === 'base64' }).then(contents => { contents.files['project.json'].async('text').then(projectJSON => { var projectData = JSON.parse(projectJSON); - if (!projectData.objects) { // No metadata! This is a pre 1.0.9a project. Convert it. console.log('Wick.WickFile: Converting old project format.'); projectData = Wick.WickFile.Alpha.convertJsonProject(projectData); } - projectData.assets = []; - for (var uuid in projectData.objects) { var data = projectData.objects[uuid]; var object = Wick.Base.fromData(data); Wick.ObjectCache.addObject(object); } - var project = Wick.Base.fromData(projectData.project); Wick.ObjectCache.addObject(project); var loadedAssetCount = 0; let corruptedFiles = []; // Store a list of all files that are now missing. - // Immediately end if the project has no assets. + // Immediately end if the project has no assets. if (project.getAssets().length === 0) { this._prepareProject(project); - callback(project); } else { // Make a copy of the assets, as we may get rid of some mid process. let allAssets = project.getAssets().concat([]); allAssets.forEach(assetData => { var assetFile = contents.files['assets/' + assetData.uuid + '.' + assetData.fileExtension]; + /** * Checks if we've loaded all assets, logs an error if an error occurred * while loading asset files. */ - var checkProjectLoad = () => { loadedAssetCount++; - if (loadedAssetCount === allAssets.length) { // Throw an error if any corrupted files were found. project.errorOccured && corruptedFiles.length > 0 && project.errorOccured("Corrupted Files Were Deleted: " + corruptedFiles); - this._prepareProject(project); - callback(project); } }; - if (!assetFile) { // Try removing the asset from the project here. assetData.removeAllInstances(); @@ -48297,7 +48130,6 @@ Wick.WickFile = class { checkProjectLoad(); return; } - assetFile.async('base64').then(assetFileData => { var assetSrc = 'data:' + assetData.MIMEType + ';base64,' + assetFileData; Wick.FileCache.addFile(assetSrc, assetData.uuid); @@ -48319,28 +48151,27 @@ Wick.WickFile = class { callback(null); }); } + /** * Create a wick file from the project. * @param {Wick.Project} project - the project to create a wick file from * @param {function} callback - Function called when the file is created. Contains the file as a parameter. * @param {string} format - The format to return. Can be 'blob' or 'base64'. */ - - static toWickFile(project, callback, format) { if (!format) { format = 'blob'; } - if (format !== 'blob' && format !== 'base64') { console.error('WickFile.toWickFile: invalid format: ' + format); return; } + var zip = new JSZip(); - var zip = new JSZip(); // Create assets folder - - var assetsFolder = zip.folder("assets"); // Populate assets folder with files + // Create assets folder + var assetsFolder = zip.folder("assets"); + // Populate assets folder with files project.getAssets().filter(asset => { return asset instanceof Wick.ImageAsset || asset instanceof Wick.SoundAsset || asset instanceof Wick.FontAsset || asset instanceof Wick.ClipAsset || asset instanceof Wick.SVGAsset; }).forEach(asset => { @@ -48357,42 +48188,36 @@ Wick.WickFile = class { objectCacheSerialized[object.uuid] = object.serialize(); }); var projectSerialized = project.serialize(); - for (var uuid in objectCacheSerialized) { if (objectCacheSerialized[uuid].classname === 'Project') { delete objectCacheSerialized[uuid]; } - } // Remove some extra data that we don't actually want to save - // Clear selection: - + } + // Remove some extra data that we don't actually want to save + // Clear selection: for (var uuid in objectCacheSerialized) { var object = objectCacheSerialized[uuid]; - if (object.classname === 'Selection') { object.selectedObjects = []; } - } // Set focus to root - - + } + // Set focus to root for (var uuid in objectCacheSerialized) { var object = objectCacheSerialized[uuid]; - if (projectSerialized.children.indexOf(uuid) !== -1 && object.classname === 'Clip') { projectSerialized.focus = uuid; } - } // Reset all playhead positions - - + } + // Reset all playhead positions for (var uuid in objectCacheSerialized) { var object = objectCacheSerialized[uuid]; - if (object.classname === 'Timeline') { object.playheadPosition = 1; } - } // Add project json to root directory of zip file - + } + // Add project json to root directory of zip file var projectData = { project: projectSerialized, objects: objectCacheSerialized @@ -48406,9 +48231,8 @@ Wick.WickFile = class { } }).then(callback); } - /* Make any small backwards compatibility fixes needed */ - + /* Make any small backwards compatibility fixes needed */ static _prepareProject(project) { // 1.16+ projects don't allow gaps between frames. Wick.ObjectCache.getAllObjects().filter(object => { @@ -48420,7 +48244,6 @@ Wick.WickFile = class { timeline.fillGapsMethod = oldFrameGapFillMethod; }); } - }; /* * Utility class to convert Pre 1.0.9a projects into the most recent format @@ -48443,25 +48266,21 @@ Wick.WickFile.Alpha = class { objects: newProjectObjects }; } - static flattenWickObject(objectJSON, parentJSON, objects) { objectJSON.children = []; if (parentJSON) parentJSON.children.push(objectJSON.uuid); objects[objectJSON.uuid] = objectJSON; - if (objectJSON.root) { objectJSON.focus = objectJSON.root.uuid; Wick.WickFile.Alpha.flattenWickObject(objectJSON.root, objectJSON, objects); delete objectJSON.root; } - if (objectJSON.assets) { objectJSON.assets.forEach(asset => { Wick.WickFile.Alpha.flattenWickObject(asset, objectJSON, objects); }); delete objectJSON.assets; } - if (objectJSON.selection) { objectJSON.selection.widgetRotation = 0; objectJSON.selection.pivotPoint = { @@ -48471,7 +48290,6 @@ Wick.WickFile.Alpha = class { Wick.WickFile.Alpha.flattenWickObject(objectJSON.selection, objectJSON, objects); delete objectJSON.selection; } - if (objectJSON.transform) { objectJSON.transformation = { x: objectJSON.transform.x, @@ -48483,53 +48301,45 @@ Wick.WickFile.Alpha = class { }; delete objectJSON.transform; } - if (objectJSON.timeline) { Wick.WickFile.Alpha.flattenWickObject(objectJSON.timeline, objectJSON, objects); delete objectJSON.timeline; } - if (objectJSON.layers) { objectJSON.layers.forEach(layer => { Wick.WickFile.Alpha.flattenWickObject(layer, objectJSON, objects); }); delete objectJSON.layers; } - if (objectJSON.frames) { objectJSON.frames.forEach(frame => { Wick.WickFile.Alpha.flattenWickObject(frame, objectJSON, objects); }); delete objectJSON.frames; } - if (objectJSON.clips) { objectJSON.clips.forEach(clip => { Wick.WickFile.Alpha.flattenWickObject(clip, objectJSON, objects); }); delete objectJSON.clips; } - if (objectJSON.paths) { objectJSON.paths.forEach(path => { Wick.WickFile.Alpha.flattenWickObject(path, objectJSON, objects); }); delete objectJSON.paths; } - if (objectJSON.tweens) { objectJSON.tweens.forEach(tween => { Wick.WickFile.Alpha.flattenWickObject(tween, objectJSON, objects); }); delete objectJSON.tweens; } - if (objectJSON.pathJSON) { objectJSON.json = objectJSON.pathJSON; delete objectJSON.pathJSON; } } - }; /* * Copyright 2020 WICKLETS LLC @@ -48564,23 +48374,19 @@ Wick.WickObjectFile = class { if (typeof wickObjectFile === 'string') { wickObjectFile = Wick.ExportUtils.dataURItoBlob(wickObjectFile); } - var fr = new FileReader(); - fr.onload = () => { var data = JSON.parse(fr.result); callback(data); }; - fr.readAsText(wickObjectFile); } + /** * Create a wick file from the project. * @param {Wick.Project} clip - the clip to create a wickobject file from * @param {string} format - Can be 'blob' or 'dataurl'. */ - - static toWickObjectFile(clip, format, callback) { if (!format) format = 'blob'; var data = clip.export(); @@ -48588,22 +48394,18 @@ Wick.WickObjectFile = class { var blob = new Blob([json], { type: "application/json" }); - if (format === 'blob') { callback(blob); } else if (format === 'dataurl') { var fr = new FileReader(); - fr.onload = function (e) { callback(e.target.result); }; - fr.readAsDataURL(blob); } else { console.error('toWickObjectFile: invalid format: ' + format); } } - }; /* * Copyright 2020 WICKLETS LLC @@ -48643,7 +48445,6 @@ Wick.HTMLExport = class { }); }, 'base64'); } - }; /* * Copyright 2020 WICKLETS LLC @@ -48677,18 +48478,15 @@ Wick.HTMLPreview = class { Wick.HTMLExport.bundleProject(project, html => { var windowFeatures = "height=" + project.height + ",width=" + project.width; var popupWindow = window.open('', '_blank', windowFeatures); - if (popupWindow) { popupWindow.document.title = project.name; popupWindow.document.open(); popupWindow.document.write(html); popupWindow.document.close(); } - callback(popupWindow); }); } - }; /* * Copyright 2019 WICKLETS LLC @@ -48722,15 +48520,13 @@ Wick.SVGFile = class { if (typeof svgFile === 'string') { svgFile = Wick.ExportUtils.dataURItoBlob(svgFile); } - var fr = new FileReader(); - fr.onload = function () { callback(fr.result); }; - fr.readAsText(svgFile); } + /** * Create a wick file from the project. * @param {Wick.Timeline} timeline - the clip to create a wickobject file from @@ -48738,8 +48534,6 @@ Wick.SVGFile = class { * @param {function(blob)} callback - function to call when done * @returns {Blob} */ - - static toSVGFile(timeline, onError, callback) { var svgString = timeline.exportSVG(onError); var blob = new Blob([svgString], { @@ -48748,7 +48542,6 @@ Wick.SVGFile = class { callback(blob); return blob; } - }; /* * Copyright 2020 WICKLETS LLC @@ -48785,10 +48578,10 @@ Wick.ImageSequence = class { onFinish } = args; var zip = new JSZip(); - let buildZip = files => { let index = 0; files.forEach(file => { + // console.log(file); var blob = Wick.ExportUtils.dataURItoBlob(file.src); let paddedNum = (index + '').padStart(12, '0'); zip.file('frame' + paddedNum + '.png', blob); @@ -48796,13 +48589,12 @@ Wick.ImageSequence = class { }); zip.generateAsync({ type: 'blob', - compression: "DEFLATE", + compression: 'DEFLATE', compressionOptions: { level: 9 } }).then(onFinish); }; - project.generateImageSequence({ width: args.width, height: args.height, @@ -48810,7 +48602,62 @@ Wick.ImageSequence = class { onProgress: onProgress }); } +}; +/* + * 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 . + */ + +/** + * Utility class for generating an image file. + */ +Wick.ImageFile = class { + /** + * Create a png sequence from a project. + * @param {Wick.Project} project - the project to create a png sequence from + * @param {function} callback - Function called when the file is created. Contains the file as a parameter. + **/ + static toPNGFile(args) { + let { + project, + onProgress, + onFinish + } = args; + + // console.log('ImageFile.toPNGFile', args); + + // var zip = new JSZip(); + let buildImage = image => { + // console.log('ImageFile.toPNGFile image', image); + + var blob = Wick.ExportUtils.dataURItoBlob(image.src); + onFinish(blob); + }; + + // console.log('ImageFile.toPNGFile', project); + + project.generateImageFile({ + width: args.width, + height: args.height, + onFinish: buildImage, + onProgress: onProgress + }); + } }; /* * Copyright 2020 WICKLETS LLC @@ -48842,7 +48689,6 @@ Wick.ZIPExport = class { }); }); } - static _downloadDependenciesFiles(done) { var list = []; var urls = ["index.html", "preloadjs.min.js", "wickengine.js"]; @@ -48859,7 +48705,6 @@ Wick.ZIPExport = class { done(results); }); } - static _bundleFilesIntoZip(wickFile, dependenciesFiles, done) { var zip = new JSZip(); dependenciesFiles.forEach(file => { @@ -48874,7 +48719,6 @@ Wick.ZIPExport = class { } }).then(done); } - }; /* * Copyright 2020 WICKLETS LLC @@ -48912,7 +48756,6 @@ Wick.Base = class { Wick._originals[this.classname] = {}; Wick._originals[this.classname] = new Wick[this.classname](); } - if (!args) args = {}; this._uuid = args.uuid || uuidv4(); this._identifier = args.identifier || null; @@ -48924,8 +48767,9 @@ Wick.Base = class { this._classname = this.classname; this._children = []; this._childrenData = null; - this._parent = null; // If this is a project, use this object, otherwise use the passed in project if provided. + this._parent = null; + // If this is a project, use this object, otherwise use the passed in project if provided. this._project = this.classname === 'Project' ? this : args.project ? args.project : null; this.needsAutosave = true; this._cachedSerializeData = null; @@ -48933,71 +48777,59 @@ Wick.Base = class { Wick.ObjectCache.addObject(this); } + /** * @param {object} data - Serialized data to use to create a new object. */ - - static fromData(data, project) { if (!data.classname) { console.warn('Wick.Base.fromData(): data was missing, did you mean to deserialize something else?'); } - if (!Wick[data.classname]) { console.warn('Tried to deserialize an object with no Wick class: ' + data.classname); } - var object = new Wick[data.classname]({ uuid: data.uuid, project: project }); object.deserialize(data); - if (data.classname === 'Project') { object.initialize(); } - return object; } + /** * Converts this Wick Base object into a plain javascript object contianing raw data (no references). * @return {object} Plain JavaScript object representing this Wick Base object. */ - - serialize(args) { // TEMPORARY: Force the cache to never be accessed. // This is because the cache was causing issues in the tests, and the // performance boost that came with the cache was not signifigant enough // to be worth fixing the bugs over... this.needsAutosave = true; - if (this.needsAutosave || !this._cachedSerializeData) { // If the cache is outdated or does not exist, reserialize and cache. var data = this._serialize(args); - this._cacheSerializeData(data); - return data; } else { // Otherwise, just read from the cache return this._cachedSerializeData; } } + /** * Parses serialized data representing Base Objects which have been serialized using the serialize function of their class. * @param {object} data Serialized data that was returned by a Base Object's serialize function. */ - - deserialize(data) { this._deserialize(data); - this._cacheSerializeData(data); } - /* The internal serialize method that actually creates the data. Every class that inherits from Base must have one of these. */ - + /* The internal serialize method that actually creates the data. Every class that inherits from Base must have one of these. */ _serialize(args) { var data = {}; data.classname = this.classname; @@ -49009,63 +48841,63 @@ Wick.Base = class { }); return data; } - /* The internal deserialize method that actually reads the data. Every class that inherits from Base must have one of these. */ - + /* The internal deserialize method that actually reads the data. Every class that inherits from Base must have one of these. */ _deserialize(data) { this._uuid = data.uuid; this._identifier = data.identifier; this._name = data.name; this._children = []; - this._childrenData = data.children; // Clear any custom attributes set by scripts + this._childrenData = data.children; + // Clear any custom attributes set by scripts var compareObj = Wick._originals[this.classname]; - for (var name in this) { if (compareObj[name] === undefined) { delete this[name]; } } } - _cacheSerializeData(data) { this._cachedSerializeData = data; this.needsAutosave = false; } + /** * Returns a copy of a Wick Base object. * @return {Wick.Base} The object resulting from the copy */ - - copy() { var data = this.serialize(); data.uuid = uuidv4(); var copy = Wick.Base.fromData(data); - copy._childrenData = null; // Copy children + copy._childrenData = null; + // Copy children this.getChildren().forEach(child => { copy.addChild(child.copy()); }); return copy; } + /** * Returns an object containing serialied data of this object, as well as all of its children. * Use this to copy entire Wick.Base objects between projects, and to export individual Clips as files. * @returns {object} The exported data. */ - - export() { var copy = this.copy(); - copy._project = this.project; // the main object + copy._project = this.project; - var object = copy.serialize(); // children + // the main object + var object = copy.serialize(); + // children var children = copy.getChildrenRecursive().map(child => { return child.serialize(); - }); // assets + }); + // assets var assets = []; copy.getChildrenRecursive().concat(copy).forEach(child => { child._project = copy._project; @@ -49081,41 +48913,40 @@ Wick.Base = class { assets: assets }; } + /** * Import data created using Wick.Base.export(). * @param {object} exportData - an object created from Wick.Base.export(). */ - - static import(exportData, project) { if (!exportData) console.error('Wick.Base.import(): exportData is required'); if (!exportData.object) console.error('Wick.Base.import(): exportData is missing data'); - if (!exportData.children) console.error('Wick.Base.import(): exportData is missing data'); // Import assets first in case the objects need them! + if (!exportData.children) console.error('Wick.Base.import(): exportData is missing data'); + // Import assets first in case the objects need them! exportData.assets.forEach(assetData => { // Don't import assets if they exist in the project already // (Assets only get reimported when objects are pasted between projects) if (project.getAssetByUUID(assetData.uuid)) { return; } - var asset = Wick.Base.fromData(assetData, project); project.addAsset(asset); }); - var object = Wick.Base.fromData(exportData.object, project); // Import children as well + var object = Wick.Base.fromData(exportData.object, project); + // Import children as well exportData.children.forEach(childData => { // Only need to call deserialize here, we just want the object to get added to ObjectCache var child = Wick.Base.fromData(childData, project); }); return object; } + /** * Marks the object as possibly changed, so that next time autosave happens, this object is written to the save. * @type {boolean} */ - - set needsAutosave(needsAutosave) { if (needsAutosave) { Wick.ObjectCache.markObjectToBeAutosaved(this); @@ -49123,167 +48954,149 @@ Wick.Base = class { Wick.ObjectCache.clearObjectToBeAutosaved(this); } } - get needsAutosave() { return Wick.ObjectCache.objectNeedsAutosave(this); } + /** * Signals if an object is removed from the project while playing. * This is a temprary variable. * @type {boolean} */ - - get removed() { return typeof this._removed === 'undefined' ? false : this._removed; } - set removed(bool) { this._removed = bool; } + /** * Returns the classname of a Wick Base object. * @type {string} */ - - get classname() { return 'Base'; } + /** * A marker if this object is temporary. Meaning it * should be garbage collected after a play. */ - - get temporary() { return this._temporary; } + /** * The uuid of a Wick Base object. * @type {string} */ - - get uuid() { return this._uuid; } + /** * Changes an object's uuid. This function should not be used consistently, as it creates an entire copy of the object * in the object cache. Avoid using this if possile. */ - - set uuid(uuid) { this._uuid = uuid; Wick.ObjectCache.addObject(this); } + /** * The name of the object that is used to access the object through scripts. Must be a valid JS variable name. * @type {string} */ - - get identifier() { return this._identifier; } - set identifier(identifier) { // Treat empty string identifier as null if (identifier === '' || identifier === null) { this._identifier = null; return; - } // Make sure the identifier doesn't squash any attributes of the window - + } - if (this._identifierNameExistsInWindowContext(identifier)) return; // Make sure the identifier will not be squashed by Wick API functions + // Make sure the identifier doesn't squash any attributes of the window + if (this._identifierNameExistsInWindowContext(identifier)) return; - if (this._identiferNameIsPartOfWickAPI(identifier)) return; // Make sure the identifier is a valid js variable name + // Make sure the identifier will not be squashed by Wick API functions + if (this._identiferNameIsPartOfWickAPI(identifier)) return; + // Make sure the identifier is a valid js variable name if (!isVarName(identifier)) { this.project && this.project.errorOccured('Identifier must be a valid variable name.'); return; - } // Make sure the identifier is not a reserved word in js - + } - if (reserved.check(identifier)) return; // Ensure no objects with duplicate identifiers can exist + // Make sure the identifier is not a reserved word in js + if (reserved.check(identifier)) return; + // Ensure no objects with duplicate identifiers can exist this._identifier = this._getUniqueIdentifier(identifier); } + /** * The name of the object. * @type {string} */ - - get name() { return this._name; } - set name(name) { if (typeof name !== 'string') return; if (name === '') this._name = null; this._name = name; } + /** * The Wick.View object that is used for rendering this object on the canvas. */ - - get view() { return this._view; } - set view(view) { if (view) view.model = this; this._view = view; } + /** * The object that is used for rendering this object in the timeline GUI. */ - - get guiElement() { return this._guiElement; } - set guiElement(guiElement) { if (guiElement) guiElement.model = this; this._guiElement = guiElement; } + /** * Returns a single child of this object with a given classname. * @param {string} classname - the classname to use */ - - getChild(classname) { return this.getChildren(classname)[0]; } + /** * Gets all children with a given classname(s). * @param {Array|string} classname - (optional) A string, or list of strings, of classnames. */ - - getChildren(classname) { // Lazily generate children list from serialized data if (this._childrenData) { this._childrenData.forEach(uuid => { this.addChild(Wick.ObjectCache.getObjectByUUID(uuid)); }); - this._childrenData = null; } - if (classname instanceof Array) { let classNames = new Set(classname); var children = []; - if (this._children !== undefined) { children = this._children.filter(child => classNames.has(child.classname)); } - return children; } else if (classname === undefined) { // Retrieve all children if no classname was given @@ -49291,16 +49104,14 @@ Wick.Base = class { } else { // Retrieve children by classname var children = this._children.filter(child => child.classname === classname); - return children || []; } } + /** * Get an array of all children of this object, and the children of those children, recursively. * @type {Wick.Base[]} */ - - getChildrenRecursive(level, original) { var children = this.getChildren(); this.getChildren().forEach(child => { @@ -49308,57 +49119,51 @@ Wick.Base = class { }); return children; } + /** * The parent of this object. * @type {Wick.Base} */ - - get parent() { return this._parent; } + /** * The parent Clip of this object. * @type {Wick.Clip} */ - - get parentClip() { return this._getParentByClassName('Clip'); } + /** * The parent Layer of this object. * @type {Wick.Layer} */ - - get parentLayer() { return this._getParentByClassName('Layer'); } + /** * The parent Frame of this object. * @type {Wick.Frame} */ - - get parentFrame() { return this._getParentByClassName('Frame'); } + /** * The parent Timeline of this object. * @type {Wick.Timeline} */ - - get parentTimeline() { return this._getParentByClassName('Timeline'); } + /** * The project that this object belongs to. Can be null if the object is not in a project. * @type {Wick.Project} */ - - get project() { if (this._project) { return this._project; @@ -49368,35 +49173,30 @@ Wick.Base = class { return null; } } + /** * Check if an object is selected or not. * @type {boolean} */ - - get isSelected() { if (!this.project) return false; return this.project.selection.isObjectSelected(this); } + /** * Add a child to this object. * @param {Wick.Base} child - the child to add. */ - - addChild(child) { var classname = child.classname; - if (!this._children) { this._children = []; } - child._parent = this; - child._setProject(this.project); - this._children.push(child); } + /** * Insert a child into this at specified index. * @@ -49411,93 +49211,71 @@ Wick.Base = class { * @param {number} index - where to add the child * @returns {boolean} - true if an item before index was moved */ - - insertChild(child, index) { var classname = child.classname; - if (child._parent === this) { let result = 0; - let old_index = this._children.indexOf(child); - if (old_index < index) { index--; result = 1; } - this._children.splice(index, 0, this._children.splice(old_index, 1)[0]); - return result; } - if (child._parent) { child._parent.removeChild(child); } - if (!this._children) { this._children = []; } - child._parent = this; - child._setProject(this.project); - this._children.splice(index, 0, child); - return 0; } + /** * Remove a child from this object. * @param {Wick.Base} child - the child to remove. */ - - removeChild(child) { if (!this._children) { return; } - child._parent = null; child._project = null; this._children = this._children.filter(seekChild => { return seekChild !== child; }); } + /** * Assets attached to this object. * @returns {Wick.Base[]} */ - - getLinkedAssets() { // Implemented by Wick.Frame and Wick.Clip return []; } - _generateView() { var viewClass = Wick.View[this.classname]; - if (viewClass) { return new viewClass(this); } else { return null; } } - _generateGUIElement() { var guiElementClass = Wick.GUIElement[this.classname]; - if (guiElementClass && guiElementClass !== Wick.Button) { return new guiElementClass(this); } else { return null; } } - _getParentByClassName(classname) { if (!this.parent) return null; - if (this.parent instanceof Wick[classname]) { return this.parent; } else { @@ -49505,7 +49283,6 @@ Wick.Base = class { return this.parent._getParentByClassName(classname); } } - _setProject(project) { this._project = project; this.getChildren().forEach(child => { @@ -49514,7 +49291,6 @@ Wick.Base = class { } }); } - _getUniqueIdentifier(identifier) { if (!this.parent) return identifier; var otherIdentifiers = this.parent.getChildren(['Clip', 'Frame', 'Button']).filter(child => { @@ -49522,14 +49298,12 @@ Wick.Base = class { }).map(child => { return child.identifier; }); - if (otherIdentifiers.indexOf(identifier) === -1) { return identifier; } else { return this._getUniqueIdentifier(identifier + '_copy'); } } - _identifierNameExistsInWindowContext(identifier) { if (window[identifier]) { return true; @@ -49537,17 +49311,14 @@ Wick.Base = class { return false; } } - _identiferNameIsPartOfWickAPI(identifier) { var globalAPI = new GlobalAPI(this); - if (globalAPI[identifier]) { return true; } else { return false; } } - }; /* * Copyright 2020 WICKLETS LLC @@ -49584,66 +49355,56 @@ Wick.Layer = class extends Wick.Base { this.hidden = args.hidden === undefined ? false : args.hidden; this.name = args.name || null; } - _serialize(args) { var data = super._serialize(args); - data.locked = this.locked; data.hidden = this.hidden; return data; } - _deserialize(data) { super._deserialize(data); - this.locked = data.locked; this.hidden = data.hidden; } - get classname() { return 'Layer'; } + /** * The frames belonging to this layer. * @type {Wick.Frame[]} */ - - get frames() { return this.getChildren('Frame'); } + /** * The order of the Layer in the timeline. * @type {number} */ - - get index() { return this.parent && this.parent.layers.indexOf(this); } + /** * Set this layer to be the active layer in its timeline. */ - - activate() { this.parent.activeLayerIndex = this.index; } + /** * True if this layer is the active layer in its timeline. * @type {boolean} */ - - get isActive() { return this.parent && this === this.parent.activeLayer; } + /** * The length of the layer in frames. * @type {number} */ - - get length() { var end = 0; this.frames.forEach(function (frame) { @@ -49653,165 +49414,153 @@ Wick.Layer = class extends Wick.Base { }); return end; } + /** * The active frame on the layer. * @type {Wick.Frame} */ - - get activeFrame() { if (!this.parent) return null; return this.getFrameAtPlayheadPosition(this.parent.playheadPosition); } + /** * Moves this layer to a different position, inserting it before/after other layers if needed. * @param {number} index - the new position to move the layer to. */ - - move(index) { this.parentTimeline.moveLayer(this, index); } + /** * Remove this layer from its timeline. */ - - remove() { this.parentTimeline.removeLayer(this); } + /** * Adds a frame to the layer. * @param {Wick.Frame} frame - The frame to add to the Layer. */ - - addFrame(frame) { this.addChild(frame); this.resolveOverlap([frame]); this.resolveGaps([frame]); } + /** * Adds a tween to the active frame of this layer (if one exists). * @param {Wick.Tween} tween - the tween to add */ - - addTween(tween) { this.activeFrame && this.activeFrame.addChild(tween); } + /** * Adds a frame to the layer. If there is an existing frame where the new frame is * inserted, then the existing frame will be cut, and the new frame will fill the * gap created by that cut. * @param {number} playheadPosition - Where to add the blank frame. */ - - insertBlankFrame(playheadPosition) { if (!playheadPosition) { throw new Error('insertBlankFrame: playheadPosition is required'); } - var frame = new Wick.Frame({ start: playheadPosition }); - this.addChild(frame); // If there is is overlap with an existing frame + this.addChild(frame); + // If there is is overlap with an existing frame var existingFrame = this.getFrameAtPlayheadPosition(playheadPosition); - if (existingFrame) { // Make sure the new frame fills the empty space frame.end = existingFrame.end; } - this.resolveOverlap([frame]); this.resolveGaps([frame]); return frame; } + /** * Removes a frame from the Layer. * @param {Wick.Frame} frame Frame to remove. */ - - removeFrame(frame) { this.removeChild(frame); this.resolveGaps(); } + /** * Gets the frame at a specific playhead position. * @param {number} playheadPosition - Playhead position to search for frame at. * @return {Wick.Frame} The frame at the given playheadPosition. */ - - getFrameAtPlayheadPosition(playheadPosition) { return this.frames.find(frame => { return frame.inPosition(playheadPosition); }) || null; } + /** * Gets all frames in the layer that are between the two given playhead positions. * @param {number} playheadPositionStart - The start of the range to search * @param {number} playheadPositionEnd - The end of the range to search * @return {Wick.Frame[]} The frames in the given range. */ - - getFramesInRange(playheadPositionStart, playheadPositionEnd) { return this.frames.filter(frame => { return frame.inRange(playheadPositionStart, playheadPositionEnd); }); } + /** * Gets all frames in the layer that are contained within the two given playhead positions. * @param {number} playheadPositionStart - The start of the range to search * @param {number} playheadPositionEnd - The end of the range to search * @return {Wick.Frame[]} The frames contained in the given range. */ - - getFramesContainedWithin(playheadPositionStart, playheadPositionEnd) { return this.frames.filter(frame => { return frame.containedWithin(playheadPositionStart, playheadPositionEnd); }); } + /** * Prevents frames from overlapping each other by removing pieces of frames that are touching. * @param {Wick.Frame[]} newOrModifiedFrames - the frames that should take precedence when determining which frames should get "eaten". */ - - resolveOverlap(newOrModifiedFrames) { - newOrModifiedFrames = newOrModifiedFrames || []; // Ensure that frames never go beyond the beginning of the timeline + newOrModifiedFrames = newOrModifiedFrames || []; + // Ensure that frames never go beyond the beginning of the timeline newOrModifiedFrames.forEach(frame => { if (frame.start <= 1) { frame.start = 1; } }); - var isEdible = existingFrame => { return newOrModifiedFrames.indexOf(existingFrame) === -1; }; - newOrModifiedFrames.forEach(frame => { // "Full eat" // The frame completely eats the other frame. var containedFrames = this.getFramesContainedWithin(frame.start, frame.end); containedFrames.filter(isEdible).forEach(existingFrame => { existingFrame.remove(); - }); // "Right eat" - // The frame takes a chunk out of the right side of another frame. + }); + // "Right eat" + // The frame takes a chunk out of the right side of another frame. this.frames.filter(isEdible).forEach(existingFrame => { if (existingFrame.inPosition(frame.start) && existingFrame.start !== frame.start) { existingFrame.end = frame.start - 1; } - }); // "Left eat" - // The frame takes a chunk out of the left side of another frame. + }); + // "Left eat" + // The frame takes a chunk out of the left side of another frame. this.frames.filter(isEdible).forEach(existingFrame => { if (existingFrame.inPosition(frame.end) && existingFrame.end !== frame.end) { existingFrame.start = frame.end + 1; @@ -49819,11 +49568,10 @@ Wick.Layer = class extends Wick.Base { }); }); } + /** * Prevents gaps between frames by extending frames to fill empty space between themselves. */ - - resolveGaps(newOrModifiedFrames) { if (this.parentTimeline && this.parentTimeline.waitToFillFrameGaps) return; newOrModifiedFrames = newOrModifiedFrames || []; @@ -49833,7 +49581,6 @@ Wick.Layer = class extends Wick.Base { // Method 1: Use the frame on the left (if there is one) to fill the gap if (fillGapsMethod === 'auto_extend') { var frameOnLeft = this.getFrameAtPlayheadPosition(gap.start - 1); - if (!frameOnLeft || newOrModifiedFrames.indexOf(frameOnLeft) !== -1 || gap.start === 1) { // If there is no frame on the left, create a blank one var empty = new Wick.Frame({ @@ -49845,9 +49592,9 @@ Wick.Layer = class extends Wick.Base { // Otherwise, extend the frame to the left to fill the gap frameOnLeft.end = gap.end; } - } // Method 2: Always create empty frames to fill gaps - + } + // Method 2: Always create empty frames to fill gaps if (fillGapsMethod === 'blank_frames') { var empty = new Wick.Frame({ start: gap.start, @@ -49857,35 +49604,32 @@ Wick.Layer = class extends Wick.Base { } }); } + /** * Generate a list of positions where there is empty space between frames. * @returns {Object[]} An array of objects with start/end positions describing gaps. */ - - findGaps() { var gaps = []; var currentGap = null; - for (var i = 1; i <= this.length; i++) { - var frame = this.getFrameAtPlayheadPosition(i); // Found the start of a gap + var frame = this.getFrameAtPlayheadPosition(i); + // Found the start of a gap if (!frame && !currentGap) { currentGap = {}; currentGap.start = i; - } // Found the end of a gap - + } + // Found the end of a gap if (frame && currentGap) { currentGap.end = i - 1; gaps.push(currentGap); currentGap = null; } } - return gaps; } - }; /* * Copyright 2020 WICKLETS LLC @@ -49918,8 +49662,13 @@ Wick.Project = class extends Wick.Base { * @param {number} framerate - Project framerate in frames-per-second. Default 12. * @param {Color} backgroundColor - Project background color in hex. Default #ffffff. */ - constructor(args) { - if (!args) args = {}; + constructor(paramArgs) { + let args = {}; + if (paramArgs) { + args = { + ...paramArgs + }; + } super(args); this._name = args.name || 'My Project'; this._width = args.width || 720; @@ -49964,7 +49713,6 @@ Wick.Project = class extends Wick.Base { this._hideCursor = false; this._muted = false; this._publishedMode = false; // Review the publishedMode setter for rules. - this._showClipBorders = true; this._userErrorCallback = null; this._tools = { @@ -49984,14 +49732,11 @@ Wick.Project = class extends Wick.Base { text: new Wick.Tools.Text(), zoom: new Wick.Tools.Zoom() }; - for (var toolName in this._tools) { this._tools[toolName].project = this; } - this.activeTool = 'cursor'; this._toolSettings = new Wick.ToolSettings(); - this._toolSettings.onSettingsChanged((name, value) => { if (name === 'fillColor') { this.selection.fillColor = value.rgba; @@ -49999,56 +49744,49 @@ Wick.Project = class extends Wick.Base { this.selection.strokeColor = value.rgba; } }); - this._playing = false; this._scriptSchedule = []; this._error = null; this.history.project = this; this.history.pushState(Wick.History.StateType.ONLY_VISIBLE_OBJECTS); } + /** * Prepares the project to be used in an editor. */ - - prepareProjectForEditor() { this.project.resetCache(); this.project.recenter(); this.project.view.prerender(); this.project.view.render(); } + /** * Used to initialize the state of elements within the project. Should only be called after * deserialization of project and all objects within the project. */ - - initialize() { // Fixing all clip positions... This should be done in an internal method when the project is done loading... this.activeFrame && this.activeFrame.clips.forEach(clip => { clip.applySingleFramePosition(); }); } + /** * Resets the cache and removes all unlinked items from the project. */ - - resetCache() { Wick.ObjectCache.removeUnusedObjects(this); } + /** * TODO: Remove all elements created by this project. */ - - destroy() { this.guiElement.removeAllEventListeners(); } - _deserialize(data) { super._deserialize(data); - this.name = data.name; this.width = data.width; this.height = data.height; @@ -50058,15 +49796,14 @@ Wick.Project = class extends Wick.Base { this._hideCursor = false; this._muted = false; this._renderBlackBars = true; - this._hitTestOptions = this.getDefaultHitTestOptions(); // reset rotation, but not pan/zoom. - // not resetting pan/zoom is convenient when preview playing. + this._hitTestOptions = this.getDefaultHitTestOptions(); + // reset rotation, but not pan/zoom. + // not resetting pan/zoom is convenient when preview playing. this.rotation = 0; } - _serialize(args) { var data = super._serialize(args); - data.name = this.name; data.width = this.width; data.height = this.height; @@ -50075,12 +49812,12 @@ Wick.Project = class extends Wick.Base { data.onionSkinEnabled = this.onionSkinEnabled; data.onionSkinSeekForwards = this.onionSkinSeekForwards; data.onionSkinSeekBackwards = this.onionSkinSeekBackwards; - data.focus = this.focus.uuid; // Save some metadata which will eventually end up in the wick file + data.focus = this.focus.uuid; + // Save some metadata which will eventually end up in the wick file data.metadata = Wick.WickFile.generateMetaData(); return data; } - getDefaultHitTestOptions() { return { mode: 'RECTANGLE', @@ -50089,301 +49826,270 @@ Wick.Project = class extends Wick.Base { intersections: false }; } - get classname() { return 'Project'; } + /** * Assign a function to be called when a user error happens (not script * errors - errors such as drawing tool errors, invalid selection props, etc) * @param {Function} fn - the function to call when errors happen */ - - onError(fn) { this._userErrorCallback = fn; } + /** * Called when an error occurs to forward to the onError function * @param {String} message - the message to display for the error */ - - errorOccured(message) { - if (this._userErrorCallback) this._userErrorCallback(message); - + if (this._userErrorCallback) { + this._userErrorCallback(message); + } this._internalErrorMessages.push(message); } + /** * The width of the project. * @type {number} */ - - get width() { return this._width; } - set width(width) { - if (typeof width !== 'number') return; - if (width < 1) width = 1; - if (width > 200000) width = 200000; - this._width = width; + let newWidth = width; + if (typeof newWidth !== 'number') { + return; + } + if (newWidth < 1) { + newWidth = 1; + } + if (newWidth > 200000) { + newWidth = 200000; + } + this._width = newWidth; } + /** * The height of the project. * @type {number} */ - - get height() { return this._height; } - set height(height) { if (typeof height !== 'number') return; if (height < 1) height = 1; if (height > 200000) height = 200000; this._height = height; } + /** * The framerate of the project. * @type {number} */ - - get framerate() { return this._framerate; } - set framerate(framerate) { if (typeof framerate !== 'number') return; if (framerate < 1) framerate = 1; if (framerate > 9999) framerate = 9999; this._framerate = framerate; } + /** * The background color of the project. * @type {string} */ - - get backgroundColor() { return this._backgroundColor; } - set backgroundColor(backgroundColor) { this._backgroundColor = backgroundColor; } - get hitTestOptions() { return this._hitTestOptions; } - set hitTestOptions(options) { if (options) { if (options.mode === 'CIRCLE' || options.mode === 'RECTANGLE' || options.mode === 'CONVEX') { this._hitTestOptions.mode = options.mode; } - if (typeof options.offset === 'boolean') { this._hitTestOptions.offset = options.offset; } - if (typeof options.overlap === 'boolean') { this._hitTestOptions.overlap = options.overlap; } - if (typeof options.intersections === 'boolean') { this._hitTestOptions.intersections = options.intersections; } } } + /** * The timeline of the active clip. * @type {Wick.Timeline} */ - - get activeTimeline() { return this.focus.timeline; } + /** * The active layer of the active timeline. * @type {Wick.Layer} */ - - get activeLayer() { return this.activeTimeline.activeLayer; } + /** * The active frame of the active layer. * @type {Wick.Frame} */ - - get activeFrame() { return this.activeLayer.activeFrame; } + /** * The active frames of the active timeline. * @type {Wick.Frame[]} */ - - get activeFrames() { return this.focus.timeline.activeFrames; } + /** * All frames in this project. * @type {Wick.Frame[]} */ - - getAllFrames() { return this.root.timeline.getAllFrames(true); } + /** * The project selection. * @type {Wick.Selection} */ - - get selection() { return this.getChild('Selection'); } - set selection(selection) { if (this.selection) { this.removeChild(this.selection); } - this.addChild(selection); } + /** * An instance of the Wick.History utility class for undo/redo functionality. * @type {Wick.History} */ - - get history() { return this._history; } - set history(history) { this._history = history; } + /** * Value used to determine the zoom of the canvas. */ - - get zoom() { return this._zoom; } - set zoom(z) { const max = this.view.calculateFitZoom() * 10; const min = .10; this._zoom = Math.max(min, Math.min(max, z)); } + /** * Undo the last action. * @returns {boolean} true if there was something to undo, false otherwise. */ - - undo() { // Undo discards in-progress brush strokes. if (this._tools.brush.isInProgress()) { this._tools.brush.discard(); - return true; } - this.selection.clear(); var success = this.project.history.popState(); return success; } + /** * Redo the last action that was undone. * @returns {boolean} true if there was something to redo, false otherwise. */ - - redo() { this.selection.clear(); var success = this.project.history.recoverState(); return success; } + /** * The assets belonging to the project. * @type {Wick.Asset[]} */ - - get assets() { return this.getChildren(['ImageAsset', 'SoundAsset', 'ClipAsset', 'FontAsset', 'SVGAsset']); } + /** * Adds an asset to the project. * @param {Wick.Asset} asset - The asset to add to the project. */ - - addAsset(asset) { if (this.assets.indexOf(asset) === -1) { this.addChild(asset); } } + /** * Removes an asset from the project. Also removes all instances of that asset from the project. * @param {Wick.Asset} asset - The asset to remove from the project. */ - - removeAsset(asset) { asset.removeAllInstances(); this.removeChild(asset); } + /** * Retrieve an asset from the project by its UUID. * @param {string} uuid - The UUID of the asset to get. * @return {Wick.Asset} The asset */ - - getAssetByUUID(uuid) { var asset = this.getAssets().find(asset => { return asset.uuid === uuid; }); - if (asset) { return asset; } else { console.warn('Wick.Project.getAssetByUUID: No asset found with uuid ' + uuid); } } + /** * Retrieve an asset from the project by its name. * @param {string} name - The name of the asset to get. * @return {Wick.Asset} The asset */ - - getAssetByName(name) { return this.getAssets().find(asset => { return asset.name === name; }); } + /** * The assets belonging to the project. * @param {string} type - Optional, filter assets by type ("Sound"/"Image"/"Clip"/"Button") * @returns {Wick.Asset[]} The assets in the project */ - - getAssets(type) { if (!type) { return this.assets; @@ -50393,68 +50099,62 @@ Wick.Project = class extends Wick.Base { }); } } + /** * A list of all "fontFamily" in the asset library. * @returns {string[]} */ - - getFonts() { return this.getAssets('Font').map(asset => { return asset.fontFamily; }); } + /** * Check if a FontAsset with a given fontFamily exists in the project. * @param {string} fontFamily - The font to check for * @returns {boolean} */ - - hasFont(fontFamily) { return this.getFonts().find(seekFontFamily => { return seekFontFamily === fontFamily; }) !== undefined; } + /** * The root clip. * @type {Wick.Clip} */ - - get root() { return this.getChild('Clip'); } - set root(root) { if (this.root) { this.removeChild(this.root); } - this.addChild(root); } + /** * The currently focused clip. * @type {Wick.Clip} */ - - get focus() { return this._focus && Wick.ObjectCache.getObjectByUUID(this._focus); } - set focus(focus) { var focusChanged = this.focus !== null && this.focus !== focus; this._focus = focus.uuid; - if (focusChanged) { - this.selection.clear(); // Reset timelines of subclips of the newly focused clip + this.selection.clear(); + // Reset timelines of subclips of the newly focused clip focus.timeline.clips.forEach(subclip => { subclip.timeline.playheadPosition = 1; subclip.applySingleFramePosition(); // Make sure to visualize single frame clips properly. - }); // Reset pan and zoom and clear selection on focus change + }); + // Reset pan and zoom and clear selection on focus change this.resetZoomAndPan(); } else { // Make sure the single frame @@ -50463,16 +50163,14 @@ Wick.Project = class extends Wick.Base { }); } } + /** * The position of the mouse * @type {object} */ - - get mousePosition() { return this._mousePosition; } - set mousePosition(mousePosition) { this._lastMousePosition = { x: this.mousePosition.x, @@ -50480,12 +50178,11 @@ Wick.Project = class extends Wick.Base { }; this._mousePosition = mousePosition; } + /** * The amount the mouse has moved in the last tick * @type {object} */ - - get mouseMove() { let moveX = this.mousePosition.x - this._lastMousePosition.x; let moveY = this.mousePosition.y - this._lastMousePosition.y; @@ -50494,93 +50191,82 @@ Wick.Project = class extends Wick.Base { y: moveY }; } + /** * Determine if the mouse is down. * @type {boolean} */ - - get isMouseDown() { return this._isMouseDown; } - set isMouseDown(isMouseDown) { this._isMouseDown = isMouseDown; } + /** * The keys that are currenty held down. * @type {string[]} */ - - get keysDown() { return this._keysDown; } - set keysDown(keysDown) { this._keysDown = keysDown; } + /** * The keys were just pressed (i.e., are currently held down, but were not last tick). * @type {string[]} */ - - get keysJustPressed() { // keys that are in _keysDown, but not in _keysLastDown return this._keysDown.filter(key => { return this._keysLastDown.indexOf(key) === -1; }); } + /** * The keys that were just released (i.e. were down last tick back are no longer down.) * @return {string[]} */ - - get keysJustReleased() { return this._keysLastDown.filter(key => { return this._keysDown.indexOf(key) === -1; }); } + /** * Check if a key is being pressed. * @param {string} key - The name of the key to check */ - - isKeyDown(key) { return this.keysDown.indexOf(key) !== -1; } + /** * Check if a key was just pressed. * @param {string} key - The name of the key to check */ - - isKeyJustPressed(key) { return this.keysJustPressed.indexOf(key) !== -1; } + /** * The key to be used in the global 'key' variable in the scripting API. Update currentKey before you run any key script. * @type {string[]} */ - - get currentKey() { return this._currentKey; } - set currentKey(currentKey) { this._currentKey = currentKey; } + /** * Creates an asset from a File object and adds that asset to the project. * @param {File} file - File object to be read and converted into an asset. * @param {function} callback Function with the created Wick Asset. Can be passed undefined on improper file input. */ - - importFile(file, callback) { let imageTypes = Wick.ImageAsset.getValidMIMETypes(); let soundTypes = Wick.SoundAsset.getValidMIMETypes(); @@ -50591,24 +50277,20 @@ Wick.Project = class extends Wick.Base { let soundExtensions = Wick.SoundAsset.getValidExtensions(); let fontExtensions = Wick.FontAsset.getValidExtensions(); let clipExtensions = Wick.ClipAsset.getValidExtensions(); - let svgExtensions = Wick.SVGAsset.getValidExtensions(); // Fix missing mimetype for wickobj files + let svgExtensions = Wick.SVGAsset.getValidExtensions(); + // Fix missing mimetype for wickobj files var type = file.type; - if (file.type === '' && file.name.endsWith('.wickobj')) { type = 'application/json'; } - - var extension = ""; - + var extension = ''; if (file.name) { extension = file.name.split('.').pop(); } else if (file.file && typeof file.file === 'string') { extension = file.file.split('.').pop(); } - let asset = undefined; - if (imageTypes.indexOf(type) !== -1 || imageExtensions.indexOf(extension) !== -1) { asset = new Wick.ImageAsset(); } else if (soundTypes.indexOf(type) !== -1 || soundExtensions.indexOf(extension) !== -1) { @@ -50620,7 +50302,6 @@ Wick.Project = class extends Wick.Base { } else if (svgTypes.indexOf(type) !== -1 || svgExtensions.indexOf(extension) !== -1) { asset = new Wick.SVGAsset(); } - if (asset === undefined) { console.warn('importFile(): Could not import file ' + file.name + ', filetype: "' + file.type + '" is not supported.'); console.warn('Supported File Types Are:', { @@ -50640,9 +50321,7 @@ Wick.Project = class extends Wick.Base { callback(null); return; } - let reader = new FileReader(); - reader.onload = () => { let dataURL = reader.result; asset.src = dataURL; @@ -50653,35 +50332,33 @@ Wick.Project = class extends Wick.Base { callback(asset); }); }; - reader.readAsDataURL(file); } + /** * True if onion skinning is on. False otherwise. */ - - get onionSkinEnabled() { return this._onionSkinEnabled; } - set onionSkinEnabled(bool) { - if (typeof bool !== "boolean") return; // Get all onion skinned frames, if we're turning off onion skinning. + if (typeof bool !== 'boolean') return; + // Get all onion skinned frames, if we're turning off onion skinning. let onionSkinnedFrames = []; if (!bool) onionSkinnedFrames = this.getAllOnionSkinnedFrames(); - this._onionSkinEnabled = bool; // Rerender any onion skinned frames. + this._onionSkinEnabled = bool; + // Rerender any onion skinned frames. onionSkinnedFrames.forEach(frame => { frame.view.render(); }); } + /** * Returns all frames that should currently be onion skinned. * @returns {Wick.Frame[]} Array of Wick frames that sould be onion skinned. */ - - getAllOnionSkinnedFrames() { let onionSkinnedFrames = []; this.activeTimeline.layers.forEach(layer => { @@ -50692,11 +50369,10 @@ Wick.Project = class extends Wick.Base { }); return onionSkinnedFrames; } + /** * Deletes all objects in the selection. */ - - deleteSelectedObjects() { var objects = this.selection.getSelectedObjects(); this.selection.clear(); @@ -50710,12 +50386,11 @@ Wick.Project = class extends Wick.Base { }); this.activeTimeline.resolveFrameGaps([]); } + /** * Perform a boolean operation on all selected paths. * @param {string} booleanOpName - The name of the boolean op function to use. See Wick.Path.booleanOp. */ - - doBooleanOperationOnSelection(booleanOpName) { var paths = this.selection.getSelectedObjects('Path'); this.selection.clear(); @@ -50725,21 +50400,18 @@ Wick.Project = class extends Wick.Base { if (paths.indexOf(path) === paths.length - 1 && booleanOpName === 'subtract') { return; } - path.remove(); }); this.activeFrame.addPath(booleanOpResult); this.selection.select(booleanOpResult); } + /** * Copy the contents of the selection to the clipboard. * @returns {boolean} True if there was something to copy, false otherwise */ - - copySelectionToClipboard() { var objects = this.selection.getSelectedObjects(); - if (objects.length === 0) { return false; } else { @@ -50747,12 +50419,11 @@ Wick.Project = class extends Wick.Base { return true; } } + /** * Copy the contents of the selection to the clipboard, and delete what was copied. * @returns {boolean} True if there was something to cut, false otherwise */ - - cutSelectionToClipboard() { if (this.copySelectionToClipboard()) { this.deleteSelectedObjects(); @@ -50761,21 +50432,19 @@ Wick.Project = class extends Wick.Base { return false; } } + /** * Paste the contents of the clipboard into the project. * @returns {boolean} True if there was something to paste in the clipboard, false otherwise. */ - - pasteClipboardContents() { return this.clipboard.pasteObjectsFromClipboard(this); } + /** * Copy and paste the current selection. * @returns {boolean} True if there was something to duplicate, false otherwise */ - - duplicateSelection() { if (!this.copySelectionToClipboard()) { return false; @@ -50783,35 +50452,29 @@ Wick.Project = class extends Wick.Base { return this.pasteClipboardContents(); } } + /** * Move the current selection above, below, or inside target. * @param {object} target - The target object (to become parent of selection) * @param {number} index - index to insert at * @returns {boolean} - true if project did change */ - - moveSelection(target, index) { // Indices give us a way to order the selection from top to bottom let get_indices = obj => { var indices = []; - while (obj.parent !== null) { let parent = obj.parent; - if (parent.classname === 'Frame') { indices.unshift(parent.getChildren().length - 1 - parent.getChildren().indexOf(obj)); } else { indices.unshift(parent.getChildren().indexOf(obj)); } - obj = parent; } - return indices; - }; // Assumes i1, i2 same length, ordering same as outliner - - + }; + // Assumes i1, i2 same length, ordering same as outliner let compare_indices = (i1, i2) => { for (let i = 0; i < i1.length; i++) { if (i1[i] < i2[i]) { @@ -50820,67 +50483,56 @@ Wick.Project = class extends Wick.Base { return -1; } } - return 0; }; - let selection = this.selection.getSelectedObjects(); - if (selection.length === 0) { return false; } - let selection_indices = selection.map(get_indices); let l = selection_indices[0].length; - for (let i = 0; i < selection_indices.length; i++) { if (selection_indices[i].length !== l) { // Must all have the same depth return false; } } - let zip = selection_indices.map((o, i) => { return [o, selection[i]]; }); zip.sort(([i1], [i2]) => compare_indices(i1, i2)); - if (target.classname === 'Frame') { // Render order is reversed for children of frames zip.reverse(); } - for (let i = 0; i < zip.length; i++) { let [, obj] = zip[i]; index -= target.insertChild(obj, index) ? 1 : 0; } - return true; } + /** * Cut the currently selected frames. */ - - cutSelectedFrames() { this.selection.getSelectedObjects('Frame').forEach(frame => { frame.cut(); }); } + /** * Inserts a blank frame into the timeline at the position of the playhead. * If the playhead is over an existing frame, that frame will be cut in half, * and a blank frame will be added to fill the empty space created by the cut. */ - - insertBlankFrame() { var playheadPosition = this.activeTimeline.playheadPosition; - var newFrames = []; // Insert new frames + var newFrames = []; + // Insert new frames if (this.selection.numObjects > 0) { // Insert frames on all frames that are both active and selected - /*this.activeTimeline.activeFrames.filter(frame => { return frame.isSelected; }).forEach(frame => { @@ -50892,21 +50544,19 @@ Wick.Project = class extends Wick.Base { } else { // Insert one frame on the active layer newFrames.push(this.activeLayer.insertBlankFrame(playheadPosition)); - } // Select the newly added frames - + } + // Select the newly added frames this.selection.clear(); this.selection.selectMultipleObjects(newFrames); } + /** * A tween can be created if frames are selected or if there is a frame under the playhead on the active layer. */ - - get canCreateTween() { // Frames are selected, a tween can be created var selectedFrames = this.selection.getSelectedObjects('Frame'); - if (selectedFrames.length > 0) { // Make sure you can only create tweens on contentful frames if (selectedFrames.find(frame => { @@ -50916,26 +50566,22 @@ Wick.Project = class extends Wick.Base { } else { return true; } - } // There is a frame under the playhead on the active layer, a tween can be created - + } + // There is a frame under the playhead on the active layer, a tween can be created var activeFrame = this.activeLayer.activeFrame; - if (activeFrame) { // ...but only if that frame is contentful return activeFrame.contentful; } - return false; } + /** * Create a new tween on all selected frames OR on the active frame of the active layer. */ - - createTween() { var selectedFrames = this.selection.getSelectedObjects('Frame'); - if (selectedFrames.length > 0) { // Create a tween on all selected frames this.selection.getSelectedObjects('Frame').forEach(frame => { @@ -50946,23 +50592,20 @@ Wick.Project = class extends Wick.Base { this.activeLayer.activeFrame.createTween(); } } + /** * Tries to create a tween if there is an empty space between tweens. */ - - tryToAutoCreateTween() { var frame = this.activeFrame; - if (frame.tweens.length > 0 && !frame.getTweenAtPosition(frame.getRelativePlayheadPosition())) { frame.createTween(); } } + /** * Move the right edge of all frames right one frame. */ - - extendFrames(frames) { frames.forEach(frame => { frame.end++; @@ -50970,21 +50613,19 @@ Wick.Project = class extends Wick.Base { this.activeTimeline.resolveFrameOverlap(frames); this.activeTimeline.resolveFrameGaps(frames); } + /** * Move the right edge of all frames right one frame, and push other frames away. */ - - extendFramesAndPushOtherFrames(frames) { frames.forEach(frame => { frame.extendAndPushOtherFrames(); }); } + /** * Move the right edge of all frames left one frame. */ - - shrinkFrames(frames) { frames.forEach(frame => { if (frame.length === 1) return; @@ -50993,21 +50634,19 @@ Wick.Project = class extends Wick.Base { this.activeTimeline.resolveFrameOverlap(frames); this.activeTimeline.resolveFrameGaps(frames); } + /** * Move the right edge of all frames left one frame, and pull other frames along. */ - - shrinkFramesAndPullOtherFrames(frames) { frames.forEach(frame => { frame.shrinkAndPullOtherFrames(); }); } + /** * Shift all selected frames over one frame to the right */ - - moveSelectedFramesRight() { var frames = this.selection.getSelectedObjects('Frame'); frames.forEach(frame => { @@ -51017,11 +50656,10 @@ Wick.Project = class extends Wick.Base { this.activeTimeline.resolveFrameOverlap(frames); this.activeTimeline.resolveFrameGaps(); } + /** * Shift all selected frames over one frame to the left */ - - moveSelectedFramesLeft() { var frames = this.selection.getSelectedObjects('Frame'); frames.forEach(frame => { @@ -51031,11 +50669,10 @@ Wick.Project = class extends Wick.Base { this.activeTimeline.resolveFrameOverlap(frames); this.activeTimeline.resolveFrameGaps(); } + /** * Selects all objects that are visible on the canvas (excluding locked layers and onion skinned objects) */ - - selectAll() { let objectsToAdd = []; this.selection.clear(); @@ -51051,6 +50688,7 @@ Wick.Project = class extends Wick.Base { }); this.selection.selectMultipleObjects(objectsToAdd); } + /** * Adds an image path to the active frame using a given asset as its image src. * @param {Wick.Asset} asset - the asset to use for the image src @@ -51058,8 +50696,6 @@ Wick.Project = class extends Wick.Base { * @param {number} y - the y position to create the image path at * @param {function} callback - the function to call after the path is created. */ - - createImagePathFromAsset(asset, x, y, callback) { let playheadPosition = this.focus.timeline.playheadPosition; if (!this.activeFrame) this.activeLayer.insertBlankFrame(playheadPosition); @@ -51070,6 +50706,7 @@ Wick.Project = class extends Wick.Base { callback(path); }); } + /** * Adds an instance of a clip asset to the active frame. * @param {Wick.Asset} asset - the asset to create the clip instance from @@ -51077,8 +50714,6 @@ Wick.Project = class extends Wick.Base { * @param {number} y - the y position to create the image path at * @param {function} callback - the function to call after the path is created. */ - - createClipInstanceFromAsset(asset, x, y, callback) { let playheadPosition = this.focus.timeline.playheadPosition; if (!this.activeFrame) this.activeLayer.insertBlankFrame(playheadPosition); @@ -51089,6 +50724,7 @@ Wick.Project = class extends Wick.Base { callback(clip); }, this); } + /** * Adds an instance of a clip asset to the active frame. * @param {Wick.Asset} asset - the asset to create the SVG file instance from @@ -51096,40 +50732,32 @@ Wick.Project = class extends Wick.Base { * @param {number} y - the y position to import the SVG file at * @param {function} callback - the function to call after the path is created. */ - - createSVGInstanceFromAsset(asset, x, y, callback) { let playheadPosition = this.focus.timeline.playheadPosition; if (!this.activeFrame) this.activeLayer.insertBlankFrame(playheadPosition); asset.createInstance(svg => { this.addObject(svg); svg.x = x; - svg.y = y; //this.addObject(svg); - + svg.y = y; + //this.addObject(svg); callback(svg); }); } + /** * Creates a symbol from the objects currently selected. * @param {string} identifier - the identifier to give the new symbol * @param {string} type - "Clip" or "Button" */ - - createClipFromSelection(args) { if (!args) { args = {}; } - - ; - if (args.type !== 'Clip' && args.type !== 'Button') { console.error('createClipFromSelection: invalid type: ' + args.type); return; } - let clip; - if (args.type === 'Button') { clip = new Wick[args.type]({ project: this, @@ -51150,19 +50778,19 @@ Wick.Project = class extends Wick.Base { }) }); clip.addObjects(this.selection.getSelectedObjects('Canvas')); - } // Add the clip to the frame prior to adding objects. - + } - this.activeFrame.addClip(clip); // TODO add to asset library + // Add the clip to the frame prior to adding objects. + this.activeFrame.addClip(clip); + // TODO add to asset library this.selection.clear(); this.selection.select(clip); } + /** * Breaks selected clips into their children clips and paths. */ - - breakApartSelection() { var leftovers = []; var clips = this.selection.getSelectedObjects('Clip'); @@ -51172,44 +50800,38 @@ Wick.Project = class extends Wick.Base { }); this.selection.selectMultipleObjects(leftovers); } + /** * Sets the project focus to the timeline of the selected clip. * @returns {boolean} True if selected clip is focused, false otherwise. */ - - focusTimelineOfSelectedClip() { if (this.selection.getSelectedObject() instanceof Wick.Clip) { this.focus = this.selection.getSelectedObject(); return true; } - return false; } + /** * Sets the project focus to the parent timeline of the currently focused clip. * @returns {boolean} True if parent clip is focused, false otherwise. */ - - focusTimelineOfParentClip() { if (!this.focus.isRoot) { this.focus = this.focus.parentClip; return true; } - return false; } + /** * Plays the sound in the asset library with the given name. * @param {string} assetName - Name of the sound asset to play * @param {Object} options - options for the sound. See Wick.SoundAsset.play */ - - playSound(assetName, options) { var asset = this.getAssetByName(assetName); - if (!asset) { console.warn('playSound(): No asset with name: "' + assetName + '"'); } else if (!(asset instanceof Wick.SoundAsset)) { @@ -51218,29 +50840,25 @@ Wick.Project = class extends Wick.Base { return this.playSoundFromAsset(asset, options); } } + /** * Generates information for a single sound that is being played. * @param {Wick.Asset} asset - Asset to be played. * @param {Object} options - Options including start (ms), end (ms), offset (ms), src (sound source), filetype (string). */ - - generateSoundInfo(asset, options) { if (!asset) return {}; if (!options) options = {}; let playheadPosition = this.focus.timeline.playheadPosition; let soundStartMS = 1000 / this.framerate * (playheadPosition - 1); // Adjust by one to account for sounds on frame 1 starting at 0ms. - let soundEndMS = 0; let seekMS = options.seekMS || 0; - if (options.frame) { let soundLengthInFrames = options.frame.end - (options.frame.start - 1); soundEndMS = soundStartMS + 1000 / this.framerate * soundLengthInFrames; } else { soundEndMS = soundStartMS + asset.duration * 1000; } - let soundInfo = { playheadPosition: playheadPosition, start: soundStartMS, @@ -51251,31 +50869,28 @@ Wick.Project = class extends Wick.Base { name: asset.name, volume: options.volume || 1, playedFrom: options.playedFrom || undefined // uuid of object that played the sound. - }; + return soundInfo; } + /** * Plays a sound from a presented asset. * @param {Wick.SoundAsset} asset - Name of the sound asset to play. */ - - playSoundFromAsset(asset, options) { let soundInfo = this.generateSoundInfo(asset, options); this.soundsPlayed.push(soundInfo); return asset.play(options); } + /** * Stops sound(s) currently playing. * @param {string} assetName - The name of the SoundAsset to stop. * @param {number} id - (optional) The ID of the sound to stop. Returned by playSound. If an ID is not given, all instances of the given sound asset will be stopped. */ - - stopSound(id) { var asset = this.getAssetByName(assetName); - if (!asset) { console.warn('stopSound(): No asset with name: "' + assetName + '"'); } else if (!(asset instanceof Wick.SoundAsset)) { @@ -51284,128 +50899,113 @@ Wick.Project = class extends Wick.Base { return asset.stop(id); } } + /** * Stops all sounds playing from frames and sounds played using playSound(). */ - - stopAllSounds() { // Stop all sounds started with Wick.Project.playSound(); this.getAssets('Sound').forEach(soundAsset => { soundAsset.stop(); - }); // Stop all sounds on frames + }); + // Stop all sounds on frames this.getAllFrames().forEach(frame => { frame.stopSound(); }); } + /** * Disable all sounds from playing */ - - mute() { this._muted = true; } + /** * Enable all sounds to play */ - - unmute() { this._muted = false; } + /** * Is the project currently muted? * @type {boolean} */ - - get muted() { return this._muted; } + /** * Should the project render black bars around the canvas area? * (These only show up if the size of the window/element that the project * is inside is a different size than the project dimensions). * @type {boolean} */ - - get renderBlackBars() { return this._renderBlackBars; } - set renderBlackBars(renderBlackBars) { this._renderBlackBars = renderBlackBars; } + /** * In "Published Mode", all layers will be rendered even if they are set to be hidden. * This is enabled during GIF/Video export, and enabled when the project is run standalone. * @type {boolean} */ - - get publishedMode() { return this._publishedMode; } - set publishedMode(publishedMode) { let validModes = [false, "interactive", "imageSequence", "audioSequence"]; - if (validModes.indexOf(publishedMode) === -1) { throw new Error("Published Mode: " + publishedMode + " is invalid. Must be one of type: " + validModes); } - this._publishedMode = publishedMode; } + /** * Returns true if the project is published, false otherwise. */ - - get isPublished() { return this.publishedMode !== false; } + /** * Toggle whether or not to render borders around clips. * @type {boolean} */ - - get showClipBorders() { return this._showClipBorders; } - set showClipBorders(showClipBorders) { this._showClipBorders = showClipBorders; } + /** * The current error, if one was thrown, during the last tick. * @type {Object} */ - - get error() { return this._error; } - set error(error) { if (this._error && error) { return; - } else if (error && !this._error) {// console.error(error); + } else if (error && !this._error) { + // console.error(error); } - this._error = error; } + /** * Schedules a script to be run at the end of the current tick. * @param {string} uuid - the UUID of the object running the script. * @param {string} name - the name of the script to run, see Tickable.possibleScripts. * @param {Object} parameters - An object of key,value pairs to send as parameters to the script which runs. */ - - scheduleScript(uuid, name, parameters) { this._scriptSchedule.push({ uuid: uuid, @@ -51413,11 +51013,10 @@ Wick.Project = class extends Wick.Base { parameters: parameters }); } + /** * Run scripts in schedule, in order based on Tickable.possibleScripts. */ - - runScheduledScripts() { Wick.Tickable.possibleScripts.forEach(scriptOrderName => { this._scriptSchedule.forEach(scheduledScript => { @@ -51425,26 +51024,27 @@ Wick.Project = class extends Wick.Base { uuid, name, parameters - } = scheduledScript; // Make sure we only run the script based on the current iteration through possibleScripts + } = scheduledScript; + // Make sure we only run the script based on the current iteration through possibleScripts if (name !== scriptOrderName) { return; - } // Run the script on the corresponding object! - + } + // Run the script on the corresponding object! Wick.ObjectCache.getObjectByUUID(uuid).runScript(name, parameters); }); }); } + /** * Checks if the project is currently playing. * @type {boolean} */ - - get playing() { return this._playing; } + /** * Start playing the project. * Arguments: onError: Called when a script error occurs during a tick. @@ -51452,8 +51052,6 @@ Wick.Project = class extends Wick.Base { * onAfterTick: Called after every tick * @param {object} args - Optional arguments */ - - play(args) { if (!args) args = {}; if (!args.onError) args.onError = () => {}; @@ -51462,77 +51060,80 @@ Wick.Project = class extends Wick.Base { window._scriptOnErrorCallback = args.onError; this._playing = true; this.view.paper.view.autoUpdate = false; - if (this._tickIntervalID) { this.stop(); } - this.error = null; this.history.saveSnapshot('state-before-play'); - this.selection.clear(); // Start tick loop + this.selection.clear(); + // Start tick loop this._tickIntervalID = setInterval(() => { args.onBeforeTick(); - this.tools.interact.determineMouseTargets(); // console.time('tick'); + this.tools.interact.determineMouseTargets(); + // console.time('tick'); + var error = this.tick(); + // console.timeEnd('tick'); - var error = this.tick(); // console.timeEnd('tick'); // console.time('update'); - - this.view.paper.view.update(); // console.timeEnd('update'); + this.view.paper.view.update(); + // console.timeEnd('update'); if (error) { this.stop(); return; - } // console.time('afterTick'); - + } - args.onAfterTick(); // console.timeEnd('afterTick'); + // console.time('afterTick'); + args.onAfterTick(); + // console.timeEnd('afterTick'); }, 1000 / this.framerate); } + /** * Ticks the project. * @returns {object} An object containing information about an error, if one occured while running scripts. Null otherwise. */ - - tick() { - this.root._identifier = 'Project'; // Process input + this.root._identifier = 'Project'; + // Process input this._mousePosition = this.tools.interact.mousePosition; this._isMouseDown = this.tools.interact.mouseIsDown; this._keysDown = this.tools.interact.keysDown; this._currentKey = this.tools.interact.lastKeyDown; - this._mouseTargets = this.tools.interact.mouseTargets; // Reset scripts before ticking + this._mouseTargets = this.tools.interact.mouseTargets; - this._scriptSchedule = []; // Tick the focused clip + // Reset scripts before ticking + this._scriptSchedule = []; + // Tick the focused clip this.focus._attachChildClipReferences(); - this.focus.tick(); - this.runScheduledScripts(); // Save the current keysDown + this.runScheduledScripts(); + // Save the current keysDown this._lastMousePosition = { x: this._mousePosition.x, y: this._mousePosition.y }; this._keysLastDown = [].concat(this._keysDown); this.view.render(); - if (this._error) { return this._error; } else { return null; } } + /** * Stop playing the project. */ - - stop() { this._playing = false; - this.view.paper.view.autoUpdate = true; // Run unload scripts on all objects + this.view.paper.view.autoUpdate = true; + // Run unload scripts on all objects this.getAllFrames().forEach(frame => { frame.clips.forEach(clip => { clip.scheduleScript('unload'); @@ -51541,45 +51142,46 @@ Wick.Project = class extends Wick.Base { this.runScheduledScripts(); this.stopAllSounds(); clearInterval(this._tickIntervalID); - this._tickIntervalID = null; // Loading the snapshot to restore project state also moves the playhead back to where it was originally. - // We actually don't want this, preview play should actually move the playhead after it's stopped. + this._tickIntervalID = null; - var currentPlayhead = this.focus.timeline.playheadPosition; // Load the state of the project before it was played + // Loading the snapshot to restore project state also moves the playhead back to where it was originally. + // We actually don't want this, preview play should actually move the playhead after it's stopped. + var currentPlayhead = this.focus.timeline.playheadPosition; - this.history.loadSnapshot('state-before-play'); // Wick.ObjectCache.removeUnusedObjects(this); + // Load the state of the project before it was played + this.history.loadSnapshot('state-before-play'); + // Wick.ObjectCache.removeUnusedObjects(this); if (this.error) { // An error occured. var errorObjUUID = this._error.uuid; - var errorObj = Wick.ObjectCache.getObjectByUUID(errorObjUUID); // Focus the parent of the object that caused the error so that we can select the error-causer. + var errorObj = Wick.ObjectCache.getObjectByUUID(errorObjUUID); - this.focus = errorObj.parentClip; // Select the object that caused the error + // Focus the parent of the object that caused the error so that we can select the error-causer. + this.focus = errorObj.parentClip; + // Select the object that caused the error this.selection.clear(); this.selection.select(errorObj); window._scriptOnErrorCallback && window._scriptOnErrorCallback(this.error); } else { this.focus.timeline.playheadPosition = currentPlayhead; } - this.resetCache(); delete window._scriptOnErrorCallback; } + /** * Inject the project into an element on a webpage and start playing the project. * @param {Element} element - the element to inject the project into */ - - inject(element) { this.view.canvasContainer = element; this.view.fitMode = 'fill'; this.view.canvasBGColor = this.backgroundColor.hex; - window.onresize = function () { project.view.resize(); }; - this.view.resize(); this.view.prerender(); this.focus = this.root; @@ -51595,11 +51197,10 @@ Wick.Project = class extends Wick.Base { } }); } + /** * Sets zoom and pan such that the canvas fits in the window, with some padding. */ - - recenter() { this.pan = { x: 0, @@ -51609,11 +51210,10 @@ Wick.Project = class extends Wick.Base { this.zoom = this.view.calculateFitZoom(); this.zoom *= paddingResize; } + /** * Resets zoom and pan (zoom resets to 1.0, pan resets to (0,0)). */ - - resetZoomAndPan() { this.pan = { x: 0, @@ -51621,99 +51221,86 @@ Wick.Project = class extends Wick.Base { }; this.zoom = 1; } + /** * Zooms the canvas in. */ - - zoomIn() { this.zoom *= 1.25; } + /** * Zooms the canvas out. */ - - zoomOut() { this.zoom *= 0.8; } + /** * Resets all tools in the project. */ - - resetTools() { for (let toolName of Object.keys(this.tools)) { let tool = this.tools[toolName]; tool.reset(); } } + /** * All tools belonging to the project. * @type {Array} */ - - get tools() { return this._tools; } + /** * The tool settings for the project's tools. * @type {Wick.ToolSettings} */ - - get toolSettings() { return this._toolSettings; } + /** * The currently activated tool. * @type {Wick.Tool} */ - - get activeTool() { return this._activeTool; } - set activeTool(activeTool) { var newTool; - if (typeof activeTool === 'string') { var tool = this.tools[activeTool]; - if (!tool) { console.error('set activeTool: invalid tool: ' + activeTool); } - newTool = tool; } else { newTool = activeTool; - } // Clear selection if we changed between drawing tools - + } + // Clear selection if we changed between drawing tools if (newTool.name !== 'pan' && newTool.name !== 'eyedropper' && newTool.name !== 'cursor') { this.selection.clear(); } - this._activeTool = newTool; } + /** * Returns an object associated with this project, by uuid. * @param {string} uuid */ - - getObjectByUUID(uuid) { return Wick.ObjectCache.getObjectByUUID(uuid); } + /** * Adds an object to the project. * @param {Wick.Base} object * @return {boolean} returns true if the obejct was added successfully, false otherwise. */ - - addObject(object) { if (object instanceof Wick.Path) { this.activeFrame.addPath(object); @@ -51730,9 +51317,120 @@ Wick.Project = class extends Wick.Base { } else { return false; } - return true; } + + /** + * Create a sequence of images from every frame in the project. + * @param {object} args - Options for generating the image sequence + * @param {string} imageType - MIMEtype to use for rendered image. Defaults to 'image/png'. + * @param {function} onProgress - Function to call for image loaded, useful for progress bars? + * @param {function} onFinish - Function to call when the image is loaded. + */ + generateImageFile(args) { + let options = {}; + if (args) { + options = { + ...args + }; + } + if (!options.imageType) { + options.imageType = 'image/png'; + } + if (!options.onProgress) { + options.onProgress = () => {}; + } + if (!options.onFinish) { + options.onFinish = () => {}; + } + if (!options.width) { + options.width = this.width; + } + if (!options.height) { + options.height = this.height; + } + + // console.log('generateImageFile', options); + + var renderCopy = this; + console.log('renderCopy/project', renderCopy); + renderCopy.renderBlackBars = false; // Turn off black bars (removes black lines) + + var oldBackgroundColor = renderCopy._backgroundColor; + renderCopy._backgroundColor = new Wick.Color('#00000000'); + var oldCanvasContainer = this.view.canvasContainer; + this.history.saveSnapshot('before-gif-render'); + this.mute(); + this.selection.clear(); + // this.publishedMode = 'imageSequence'; + // this.tick(); + + // Put the project canvas inside a div that's the same size as the project + // so the frames render at the correct resolution. + let container = window.document.createElement('div'); + container.style.width = options.width / window.devicePixelRatio + 'px'; + container.style.height = options.height / window.devicePixelRatio + 'px'; + window.document.body.appendChild(container); + renderCopy.view.canvasContainer = container; + renderCopy.view.resize(); + let oldZoom = renderCopy.zoom; + + // Calculate the zoom needed to fit the project into the requested container width/height + var zoom = 1; + if (options.height < options.width) { + zoom = options.height / this.height; + } else { + zoom = options.width / this.width; + } + + // Set the initial state of the project. + renderCopy.focus = renderCopy.root; + // renderCopy.focus.timeline.playheadPosition = 1; + renderCopy.onionSkinEnabled = false; + renderCopy.zoom = zoom / window.devicePixelRatio; + renderCopy.pan = { + x: 0, + y: 0 + }; + + // renderCopy.tick(); + + // We need full control over when paper.js renders, + // if we leave autoUpdate on, it's possible to lose frames if paper.js doesnt automatically + // render as fast as we are generating the images. + // (See paper.js docs for info about autoUpdate) + renderCopy.view.paper.view.autoUpdate = false; + + // var frameImages = []; + // var numMaxFrameImages = renderCopy.focus.timeline.length; + + this.resetSoundsPlayed(); + + // Do the image render + var image = new Image(); + image.onload = () => { + // console.log('Image onload', image); + options.onProgress(1, 1); + + // reset autoUpdate back to normal + renderCopy.view.paper.view.autoUpdate = true; + this.view.canvasContainer = oldCanvasContainer; + this.view.resize(); + this.history.loadSnapshot('before-gif-render'); + // this.publishedMode = false; + this.view.render(); + renderCopy._backgroundColor = oldBackgroundColor; + renderCopy.zoom = oldZoom; + window.document.body.removeChild(container); + options.onFinish(image); + }; + + // console.log('Image src render()', image); + renderCopy.view.render(); + renderCopy.view.paper.view.update(); + image.src = renderCopy.view.canvas.toDataURL(options.imageType); + } + /** * Create a sequence of images from every frame in the project. * @param {object} args - Options for generating the image sequence @@ -51740,8 +51438,6 @@ Wick.Project = class extends Wick.Base { * @param {function} onProgress - Function to call for each image loaded, useful for progress bars * @param {function} onFinish - Function to call when the images are all loaded. */ - - generateImageSequence(args) { if (!args) args = {}; if (!args.imageType) args.imageType = 'image/png'; @@ -51756,25 +51452,26 @@ Wick.Project = class extends Wick.Base { this.history.saveSnapshot('before-gif-render'); this.mute(); this.selection.clear(); - this.publishedMode = "imageSequence"; // this.tick(); - // Put the project canvas inside a div that's the same size as the project so the frames render at the correct resolution. + this.publishedMode = "imageSequence"; + // this.tick(); + // Put the project canvas inside a div that's the same size as the project so the frames render at the correct resolution. let container = window.document.createElement('div'); container.style.width = args.width / window.devicePixelRatio + 'px'; container.style.height = args.height / window.devicePixelRatio + 'px'; window.document.body.appendChild(container); renderCopy.view.canvasContainer = container; - renderCopy.view.resize(); // Calculate the zoom needed to fit the project into the requested container width/height + renderCopy.view.resize(); + // Calculate the zoom needed to fit the project into the requested container width/height var zoom = 1; - if (args.height < args.width) { zoom = args.height / this.height; } else { zoom = args.width / this.width; - } // Set the initial state of the project. - + } + // Set the initial state of the project. renderCopy.focus = renderCopy.root; renderCopy.focus.timeline.playheadPosition = 1; renderCopy.onionSkinEnabled = false; @@ -51782,22 +51479,21 @@ Wick.Project = class extends Wick.Base { renderCopy.pan = { x: 0, y: 0 - }; // renderCopy.tick(); + }; + + // renderCopy.tick(); + // We need full control over when paper.js renders, if we leave autoUpdate on, it's possible to lose frames if paper.js doesnt automatically render as fast as we are generating the images. // (See paper.js docs for info about autoUpdate) - renderCopy.view.paper.view.autoUpdate = false; var frameImages = []; var numMaxFrameImages = renderCopy.focus.timeline.length; - var renderFrame = () => { var frameImage = new Image(); - frameImage.onload = () => { frameImages.push(frameImage); var currentPos = renderCopy.focus.timeline.playheadPosition; args.onProgress(currentPos, numMaxFrameImages); - if (currentPos >= numMaxFrameImages) { // reset autoUpdate back to normal renderCopy.view.paper.view.autoUpdate = true; @@ -51815,24 +51511,20 @@ Wick.Project = class extends Wick.Base { renderFrame(); } }; - renderCopy.view.render(); renderCopy.view.paper.view.update(); frameImage.src = renderCopy.view.canvas.toDataURL(args.imageType); }; - this.resetSoundsPlayed(); renderFrame(); } - resetSoundsPlayed() { this.soundsPlayed = []; } + /** * Play the project through to generate an audio track. */ - - generateAudioSequence(args) { if (!args) args = {}; if (!args.onProgress) args.onProgress = (frame, maxFrames) => {}; @@ -51842,25 +51534,27 @@ Wick.Project = class extends Wick.Base { this.history.saveSnapshot('before-audio-render'); this.mute(); this.selection.clear(); - this.publishedMode = "audioSequence"; // Put the project canvas inside a div that's the same size as the project so the frames render at the correct resolution. + this.publishedMode = "audioSequence"; + // Put the project canvas inside a div that's the same size as the project so the frames render at the correct resolution. let container = window.document.createElement('div'); container.style.width = args.width / window.devicePixelRatio + 'px'; container.style.height = args.height / window.devicePixelRatio + 'px'; window.document.body.appendChild(container); renderCopy.view.canvasContainer = container; - renderCopy.view.resize(); // Set the initial state of the project. + renderCopy.view.resize(); + // Set the initial state of the project. renderCopy.focus = renderCopy.root; - renderCopy.focus.timeline.playheadPosition = 1; // renderCopy.tick(); // This is commented out to not miss frame 1. + renderCopy.focus.timeline.playheadPosition = 1; + + // renderCopy.tick(); // This is commented out to not miss frame 1. renderCopy.view.paper.view.autoUpdate = false; var numMaxFrameImages = renderCopy.focus.timeline.length; - var renderFrame = () => { var currentPos = renderCopy.focus.timeline.playheadPosition; args.onProgress(currentPos, numMaxFrameImages); - if (currentPos >= numMaxFrameImages) { // reset autoUpdate back to normal renderCopy.view.paper.view.autoUpdate = true; @@ -51878,10 +51572,10 @@ Wick.Project = class extends Wick.Base { renderFrame(); } }; - this.resetSoundsPlayed(); renderFrame(); } + /** * Create an object containing info on all sounds in the project. * Format: @@ -51891,8 +51585,6 @@ Wick.Project = class extends Wick.Base { * src: The source of the sound as a dataURL. * filetype: The file type of the sound asset. */ - - getAudioInfo() { return this.root.timeline.frames.filter(frame => { return frame.sound !== null; @@ -51906,13 +51598,12 @@ Wick.Project = class extends Wick.Base { }; }); } + /** * Generate an audiobuffer containing all the project's sounds merged together. * @param {object} args - takes soundInfo (list of soundInfo to use for audioGeneration). * @param {Function} callback - callback used to recieve the final audiobuffer. */ - - generateAudioTrack(args, callback) { var audioTrack = new Wick.AudioTrack(this); audioTrack.toAudioBuffer({ @@ -51921,65 +51612,57 @@ Wick.Project = class extends Wick.Base { onProgress: args.onProgress }); } + /** * Check if an object is a mouse target (if the mouse is currently hovered over the object) * @param {Wick.Tickable} object - the object to check if it is a mouse target */ - - objectIsMouseTarget(object) { return this._mouseTargets.indexOf(object) !== -1; } + /** * Whether or not to hide the cursor while project is playing. * @type {boolean} */ - - get hideCursor() { return this._hideCursor; } - set hideCursor(hideCursor) { this._hideCursor = hideCursor; } + /** * Returns true if there is currently an active frame to draw onto. * @type {boolean} */ - - get canDraw() { return !this.activeLayer.locked && !this.activeLayer.hidden; } + /** * Loads all Assets in the project's asset library. This must be called after opening a project. * @param {function} callback - Called when all assets are done loading. */ - - loadAssets(callback) { if (this.assets.length === 0) { callback(); return; } - var loadedAssetCount = 0; this.assets.forEach(asset => { asset.load(() => { loadedAssetCount++; - if (loadedAssetCount === this.assets.length) { callback(); } }); }); } + /** * Remove assets from the project that are never used. */ - - cleanupUnusedAssets() { this.assets.forEach(asset => { if (!asset.hasInstances()) { @@ -51987,7 +51670,6 @@ Wick.Project = class extends Wick.Base { } }); } - }; /* * Copyright 2020 WICKLETS LLC @@ -52015,11 +51697,10 @@ Wick.Selection = class extends Wick.Base { static get LOCATION_NAMES() { return ['Canvas', 'Timeline', 'AssetLibrary']; } + /** * Create a Wick Selection. */ - - constructor(args) { if (!args) args = {}; super(args); @@ -52034,10 +51715,8 @@ Wick.Selection = class extends Wick.Base { this.SELECTABLE_OBJECT_TYPES = ['Path', 'Clip', 'Frame', 'Tween', 'Layer', 'Asset', 'Button', 'ClipAsset', 'FileAsset', 'FontAsset', 'GIFAsset', 'ImageAsset', 'SoundAsset', 'SVGAsset']; this.SELECTABLE_OBJECT_TYPES_SET = new Set(this.SELECTABLE_OBJECT_TYPES); } - _serialize(args) { var data = super._serialize(args); - data.selectedObjects = Array.from(this._selectedObjectsUUIDs); data.widgetRotation = this._widgetRotation; data.pivotPoint = { @@ -52048,10 +51727,8 @@ Wick.Selection = class extends Wick.Base { data.originalHeight = this._originalHeight; return data; } - _deserialize(data) { super._deserialize(data); - this._selectedObjectsUUIDs = data.selectedObjects || []; this._widgetRotation = data.widgetRotation; this._pivotPoint = { @@ -52061,79 +51738,73 @@ Wick.Selection = class extends Wick.Base { this._originalWidth = data.originalWidth; this._originalHeight = data.originalHeight; } - get classname() { return 'Selection'; } + /** * The names of all attributes of the selection that can be changed. * @type {string[]} */ - - get allAttributeNames() { return ["strokeWidth", "fillColor", "strokeColor", "name", "filename", "fontSize", "fontFamily", "fontWeight", "fontStyle", "src", "frameLength", "x", "y", "originX", "originY", "width", "height", "rotation", "opacity", "sound", "soundVolume", "soundStart", "identifier", "easingType", "fullRotations", "scaleX", "scaleY", "animationType", "singleFrameNumber", "isSynced"]; } + /** * Returns true if an object is selectable. * @param {object} object object to check if selectable * @returns {boolean} true if selectable, false otherwise. */ - - isSelectable(object) { return this.SELECTABLE_OBJECT_TYPES_SET.has(object.classname); } + /** * Add a wick object to the selection. If selecting multiple objects, you should use * selection.selectMultipleObjects. * @param {Wick.Base} object - The object to select. */ - - select(object) { // Only allow specific objects to be selectable. if (!this.isSelectable(object)) { console.warn("Tried to select a " + object.classname + " object. This type is not selectable"); return; - } // Don't do anything if the object is already selected - + } + // Don't do anything if the object is already selected if (this.isObjectSelected(object)) { return; - } // Activate the cursor tool when selection changes - + } + // Activate the cursor tool when selection changes if (this._locationOf(object) === 'Canvas') { this.project.activeTool = this.project.tools.cursor; object.parentLayer && object.parentLayer.activate(); - } // Only allow selection of objects of in the same location - + } + // Only allow selection of objects of in the same location if (this._locationOf(object) !== this.location) { this.clear(); - } // Add the object to the selection! - - - this._selectedObjectsUUIDs.push(object.uuid); // Select in between frames (for shift+click selecting frames) + } + // Add the object to the selection! + this._selectedObjectsUUIDs.push(object.uuid); + // Select in between frames (for shift+click selecting frames) if (object instanceof Wick.Frame) { this._selectInBetweenFrames(object); } + this._resetPositioningValues(); - this._resetPositioningValues(); // Make sure the view gets updated the next time its needed... - - + // Make sure the view gets updated the next time its needed... this.view.dirty = true; } + /** * Select multiple objects. Must be selectable objects. Significantly faster than selecting multiple elements * with a select() independently. * @param {object[]} objects */ - - selectMultipleObjects(objects) { let UUIDsToAdd = []; objects.forEach(obj => { @@ -52143,82 +51814,69 @@ Wick.Selection = class extends Wick.Base { if (this.location !== this._locationOf(obj)) { this.clear(); } - UUIDsToAdd.push(obj.uuid); } }); UUIDsToAdd.forEach(uuid => { this._selectedObjectsUUIDs.push(uuid); }); - this._resetPositioningValues(); - this.view.dirty = true; } + /** * Remove a wick object from the selection. * @param {Wick.Base} object - The object to deselect. */ - - deselect(object) { this._selectedObjectsUUIDs = this._selectedObjectsUUIDs.filter(uuid => { return uuid !== object.uuid; }); + this._resetPositioningValues(); - this._resetPositioningValues(); // Make sure the view gets updated the next time its needed... - - + // Make sure the view gets updated the next time its needed... this.view.dirty = true; } + /** * Remove multiple objects from the selection. Does nothing if an object is not selected. * @param {object[]} objects objects to remove from the selection. */ - - deselectMultipleObjects(objects) { objects = objects.filter(obj => obj); let uuids = objects.map(obj => obj.uuid); uuids = new Set(uuids); this._selectedObjectsUUIDs = this._selectedObjectsUUIDs.filter(uuid => !uuids.has(uuid)); - this._resetPositioningValues(); - this.view.dirty = true; } + /** * Remove all objects from the selection with an optional filter. * @param {string} filter - A location or a type (see SELECTABLE_OBJECT_TYPES and LOCATION_NAMES) */ - - clear(filter) { if (filter === undefined) { this._selectedObjectsUUIDs = []; - this._resetPositioningValues(); - this.view.dirty = true; } else { this.deselectMultipleObjects(this.project.selection.getSelectedObjects(filter)); } } + /** * Checks if a given object is selected. * @param {Wick.Base} object - The object to check selection of. */ - - isObjectSelected(object) { return this._selectedObjectsUUIDs.indexOf(object.uuid) !== -1; } + /** * Get the first object in the selection if there is a single object in the selection. * @return {Wick.Base} The first object in the selection. */ - - getSelectedObject() { if (this.numObjects === 1) { return this.getSelectedObjects()[0]; @@ -52226,21 +51884,18 @@ Wick.Selection = class extends Wick.Base { return null; } } + /** * Get the objects in the selection with an optional filter. * @param {string} filter - A location or a type (see SELECTABLE_OBJECT_TYPES and LOCATION_NAMES) * @return {Wick.Base[]} The selected objects. */ - - getSelectedObjects(filter) { var objects = this._selectedObjectsUUIDs.map(uuid => { return Wick.ObjectCache.getObjectByUUID(uuid); }); - if (Wick.Selection.LOCATION_NAMES.indexOf(filter) !== -1) { var location = filter; - if (this.location !== location) { return []; } else { @@ -52252,37 +51907,33 @@ Wick.Selection = class extends Wick.Base { return object instanceof Wick[classname]; }); } - return objects; } + /** * Get the UUIDs of the objects in the selection with an optional filter. * @param {string} filter - A location or a type (see SELECTABLE_OBJECT_TYPES and LOCATION_NAMES) * @return {string[]} The UUIDs of the selected objects. */ - - getSelectedObjectUUIDs(filter) { return this.getSelectedObjects(filter).map(object => { return object.uuid; }); } + /** * The location of the objects in the selection. (see LOCATION_NAMES) * @type {string} */ - - get location() { if (this.numObjects === 0) return null; return this._locationOf(this.getSelectedObjects()[0]); } + /** * The types of the objects in the selection. (see SELECTABLE_OBJECT_TYPES) * @type {string[]} */ - - get types() { var types = this.getSelectedObjects().map(object => { return object.classname; @@ -52290,19 +51941,16 @@ Wick.Selection = class extends Wick.Base { var uniqueTypes = [...new Set(types)]; return uniqueTypes; } + /** * A single string describing the contents of the selection. * @type {string} */ - - get selectionType() { let selection = this; - if (selection.location === 'Canvas') { if (selection.numObjects === 1) { var selectedObject = selection.getSelectedObject(); - if (selectedObject instanceof window.Wick.Path) { return selectedObject.pathType; } else if (selectedObject instanceof window.Wick.Button) { @@ -52353,47 +52001,41 @@ Wick.Selection = class extends Wick.Base { return 'unknown'; } } + /** * The number of objects in the selection. * @type {number} */ - - get numObjects() { return this._selectedObjectsUUIDs.length; } + /** * The rotation of the selection (used for canvas selections) * @type {number} */ - - get widgetRotation() { return this._widgetRotation; } - set widgetRotation(widgetRotation) { this._widgetRotation = widgetRotation; } + /** * The point that transformations to the selection will be based around. * @type {object} */ - - get pivotPoint() { return this._pivotPoint; } - set pivotPoint(pivotPoint) { this._pivotPoint = pivotPoint; } + /** * The animation type of a clip. * @type {string} */ - - get animationType() { if (this.getSelectedObject() && this.selectionType === 'clip') { return this.getSelectedObject().animationType; @@ -52401,7 +52043,6 @@ Wick.Selection = class extends Wick.Base { return null; } } - set animationType(newType) { if (this.getSelectedObject()) { this.getSelectedObject().animationType = newType; @@ -52409,11 +52050,10 @@ Wick.Selection = class extends Wick.Base { console.error("Cannot set the animation type of multiple objects..."); } } + /** * If a clip is set to singleFrame, this number will be used to determine that frame. */ - - get singleFrameNumber() { if (this.getSelectedObject() && this.selectionType === 'clip') { return this.getSelectedObject().singleFrameNumber; @@ -52421,7 +52061,6 @@ Wick.Selection = class extends Wick.Base { return null; } } - set singleFrameNumber(frame) { if (this.getSelectedObject()) { this.getSelectedObject().singleFrameNumber = frame; @@ -52429,40 +52068,35 @@ Wick.Selection = class extends Wick.Base { console.error("Cannot set singleFrameNumber of multiple objects..."); } } + /** * The position of the selection. * @type {number} */ - - get x() { return this.view.x; } - set x(x) { this.view.x = x; this.project.tryToAutoCreateTween(); } + /** * The position of the selection. * @type {number} */ - - get y() { return this.view.y; } - set y(y) { this.view.y = y; this.project.tryToAutoCreateTween(); } + /** * The origin position the selection. * @type {number} */ - - get originX() { // If there's only 1 object selected, the origin is that object's position. if (this.getSelectedObject() && (this.selectionType === "clip" || this.selectionType === "button")) { @@ -52471,7 +52105,6 @@ Wick.Selection = class extends Wick.Base { return this.x + this.width / 2; } } - set originX(x) { if (this.getSelectedObject() && (this.selectionType === "clip" || this.selectionType === "button")) { this.getSelectedObject().x = x; @@ -52483,12 +52116,11 @@ Wick.Selection = class extends Wick.Base { this.x = x - this.width / 2; } } + /** * The origin position the selection. * @type {number} */ - - get originY() { // If there's only 1 object selected, the origin is that object's position. if (this.getSelectedObject() && (this.selectionType === "clip" || this.selectionType === "button")) { @@ -52497,7 +52129,6 @@ Wick.Selection = class extends Wick.Base { return this.y + this.height / 2; } } - set originY(y) { if (this.getSelectedObject() && (this.selectionType === "clip" || this.selectionType === "button")) { this.getSelectedObject().y = y; @@ -52509,80 +52140,69 @@ Wick.Selection = class extends Wick.Base { this.y = y - this.height / 2; } } + /** * The width of the selection. * @type {number} */ - - get width() { return this.view.width; } - set width(width) { this.project.tryToAutoCreateTween(); this.view.width = width; } + /** * The height of the selection. * @type {number} */ - - get height() { return this.view.height; } - set height(height) { this.project.tryToAutoCreateTween(); this.view.height = height; } + /** * The rotation of the selection. * @type {number} */ - - get rotation() { return this.view.rotation; } - set rotation(rotation) { this.project.tryToAutoCreateTween(); this.view.rotation = rotation; } + /** * It is the original width of the selection at creation. * @type {number} */ - - get originalWidth() { return this._originalWidth; } - set originalWidth(originalWidth) { this._originalWidth = originalWidth; } + /** * It is the original height of the selection at creation. * @type {number} */ - - get originalHeight() { return this._originalHeight; } - set originalHeight(originalHeight) { this._originalHeight = originalHeight; } + /** * The scale of the selection on the X axis. * @type {number} */ - - get scaleX() { // Clips store their scale state internally if (this.selectionType === "clip" || this.selectionType === "button") { @@ -52592,7 +52212,6 @@ Wick.Selection = class extends Wick.Base { return this.width / this.originalWidth; } } - set scaleX(scaleX) { // Clips store their scale state internally if (this.selectionType === "clip" || this.selectionType === "button") { @@ -52601,12 +52220,11 @@ Wick.Selection = class extends Wick.Base { this.width = this.originalWidth * scaleX; } } + /** * The scale of the selection on the Y axis. * @type {number} */ - - get scaleY() { // Clips store their scale state internally if (this.selectionType === "clip" || this.selectionType === "button") { @@ -52615,7 +52233,6 @@ Wick.Selection = class extends Wick.Base { return this.height / this.originalHeight; } } - set scaleY(scaleY) { // Clips store their scale state internally if (this.selectionType === "clip" || this.selectionType === "button") { @@ -52624,11 +52241,10 @@ Wick.Selection = class extends Wick.Base { this.height = this.originalHeight * scaleY; } } + /** * Determines if a clip is synced to the timeline. */ - - get isSynced() { // Clips store can be synced to the animation timeline if (this.selectionType === "clip") { @@ -52637,354 +52253,300 @@ Wick.Selection = class extends Wick.Base { return false; } } - set isSynced(syncBool) { if (!typeof syncBool === "boolean") return; - if (this.selectionType === "clip") { this.getSelectedObject().isSynced = syncBool; } } + /** * Flips the selected obejcts horizontally. */ - - flipHorizontally() { this.project.tryToAutoCreateTween(); this.view.flipHorizontally(); } + /** * Flips the selected obejcts vertically. */ - - flipVertically() { this.project.tryToAutoCreateTween(); this.view.flipVertically(); } + /** * Sends the selected objects to the back. */ - - sendToBack() { this.view.sendToBack(); } + /** * Brings the selected objects to the front. */ - - bringToFront() { this.view.bringToFront(); } + /** * Moves the selected objects forwards. */ - - moveForwards() { this.view.moveForwards(); } + /** * Moves the selected objects backwards. */ - - moveBackwards() { this.view.moveBackwards(); } + /** * The identifier of the selected object. * @type {string} */ - - get identifier() { return this._getSingleAttribute('identifier'); } - set identifier(identifier) { this._setSingleAttribute('identifier', identifier); } + /** * The name of the selected object. * @type {string} */ - - get name() { return this._getSingleAttribute('name'); } - set name(name) { this._setSingleAttribute('name', name); } + /** * The fill color of the selected object. * @type {paper.Color} */ - - get fillColor() { return this._getSingleAttribute('fillColor'); } - set fillColor(fillColor) { this._setSingleAttribute('fillColor', fillColor); } + /** * The stroke color of the selected object. * @type {paper.Color} */ - - get strokeColor() { return this._getSingleAttribute('strokeColor'); } - set strokeColor(strokeColor) { this._setSingleAttribute('strokeColor', strokeColor); } + /** * The stroke width of the selected object. * @type {number} */ - - get strokeWidth() { return this._getSingleAttribute('strokeWidth'); } - set strokeWidth(strokeWidth) { this._setSingleAttribute('strokeWidth', strokeWidth); } + /** * The font family of the selected object. * @type {string} */ - - get fontFamily() { return this._getSingleAttribute('fontFamily'); } - set fontFamily(fontFamily) { this._setSingleAttribute('fontFamily', fontFamily); } + /** * The font size of the selected object. * @type {number} */ - - get fontSize() { return this._getSingleAttribute('fontSize'); } - set fontSize(fontSize) { this._setSingleAttribute('fontSize', fontSize); } + /** * The font weight of the selected object. * @type {number} */ - - get fontWeight() { return this._getSingleAttribute('fontWeight'); } - set fontWeight(fontWeight) { this._setSingleAttribute('fontWeight', fontWeight); } + /** * The font style of the selected object. ('italic' or 'oblique') * @type {string} */ - - get fontStyle() { return this._getSingleAttribute('fontStyle'); } - set fontStyle(fontStyle) { this._setSingleAttribute('fontStyle', fontStyle); } + /** * The opacity of the selected object. * @type {number} */ - - get opacity() { return this._getSingleAttribute('opacity'); } - set opacity(opacity) { this.project.tryToAutoCreateTween(); - this._setSingleAttribute('opacity', opacity); } + /** * The sound attached to the selected frame. * @type {Wick.SoundAsset} */ - - get sound() { return this._getSingleAttribute('sound'); } - set sound(sound) { this._setSingleAttribute('sound', sound); } + /** * The length of the selected frame. * @type {number} */ - - get frameLength() { return this._getSingleAttribute('length'); } - set frameLength(frameLength) { this._setSingleAttribute('length', frameLength); - var layer = this.project.activeLayer; layer.resolveOverlap(this.getSelectedObjects()); layer.resolveGaps(); } + /** * The volume of the sound attached to the selected frame. * @type {number} */ - - get soundVolume() { return this._getSingleAttribute('soundVolume'); } - set soundVolume(soundVolume) { this._setSingleAttribute('soundVolume', soundVolume); } + /** * The starting position of the sound on the frame in ms. * @type {number} */ - - get soundStart() { return this._getSingleAttribute('soundStart'); } - set soundStart(soundStart) { this._setSingleAttribute('soundStart', soundStart); } + /** * The easing type of a selected tween. See Wick.Tween.VALID_EASING_TYPES. * @type {string} */ - - get easingType() { return this._getSingleAttribute('easingType'); } - set easingType(easingType) { return this._setSingleAttribute('easingType', easingType); } + /** * The amount of rotations to perform during a tween. Positive value = clockwise rotation. * @type {Number} */ - - get fullRotations() { return this._getSingleAttribute('fullRotations'); } - set fullRotations(fullRotations) { return this._setSingleAttribute('fullRotations', fullRotations); } + /** * The filename of the selected asset. Read only. * @type {string} */ - - get filename() { return this._getSingleAttribute('filename'); } + /** * True if the selection is scriptable. Read only. * @type {boolean} */ - - get isScriptable() { return this.numObjects === 1 && this.getSelectedObjects()[0].isScriptable; } + /** * The source (dataURL) of the selected ImageAsset or SoundAsset. Read only. * @type {string} */ - - get src() { return this.numObjects === 1 && this.getSelectedObjects()[0].src; } + /** * Get a list of only the farthest right frames on each layer. * @returns {Wick.Frame[]} */ - - getRightmostFrames() { var selectedFrames = this.getSelectedObjects('Frame'); var rightmostFrames = {}; selectedFrames.forEach(frame => { var layerid = frame.parentLayer.uuid; - if (!rightmostFrames[layerid] || frame.end > rightmostFrames[layerid].end) { rightmostFrames[layerid] = frame; } }); var result = []; - for (var id in rightmostFrames) { result.push(rightmostFrames[id]); } - return result; } + /** * Get a list of only the farthest left frames on each layer. * @returns {Wick.Frame[]} */ - - getLeftmostFrames() { var selectedFrames = this.getSelectedObjects('Frame'); var leftmostFrames = {}; selectedFrames.forEach(frame => { var layerid = frame.parentLayer.uuid; - if (!leftmostFrames[layerid] || frame.start < leftmostFrames[layerid].end) { leftmostFrames[layerid] = frame; } }); var result = []; - for (var id in leftmostFrames) { result.push(leftmostFrames[id]); } - return result; } - _locationOf(object) { if (object instanceof Wick.Frame || object instanceof Wick.Tween || object instanceof Wick.Layer) { return 'Timeline'; @@ -52994,12 +52556,10 @@ Wick.Selection = class extends Wick.Base { return 'Canvas'; } } - /* Helper function: Calculate the selection x,y */ - + /* Helper function: Calculate the selection x,y */ _resetPositioningValues() { var selectedObject = this.getSelectedObject(); - if (selectedObject instanceof Wick.Clip) { // Single clip selected: Use that Clip's transformation for the pivot point and rotation this._widgetRotation = selectedObject.transformation.rotation; @@ -53010,70 +52570,64 @@ Wick.Selection = class extends Wick.Base { } else { // Path selected or multiple objects selected: Reset rotation and use center for pivot point this._widgetRotation = 0; - var boundsCenter = this.view._getSelectedObjectsBounds().center; - this._pivotPoint = { x: boundsCenter.x, y: boundsCenter.y - }; // Always pull original size values. + }; + // Always pull original size values. this._originalWidth = this.view._getSelectedObjectsBounds().width; this._originalHeight = this.view._getSelectedObjectsBounds().height; } } - /* helper function for getting a single value from multiple selected objects */ - + /* helper function for getting a single value from multiple selected objects */ _getSingleAttribute(attributeName) { if (this.numObjects === 0) return null; return this.getSelectedObjects()[0][attributeName]; } - /* helper function for updating the same attribute on all items in the selection */ - + /* helper function for updating the same attribute on all items in the selection */ _setSingleAttribute(attributeName, value) { this.getSelectedObjects().forEach(selectedObject => { selectedObject[attributeName] = value; }); } - /*helper function for shift+selecting frames*/ - + /*helper function for shift+selecting frames*/ _selectInBetweenFrames(selectedFrame) { var frameBounds = { playheadStart: null, playheadEnd: null - }; // Calculate bounding box of all selected frames + }; + // Calculate bounding box of all selected frames var selectedFrames = this.getSelectedObjects('Frame'); selectedFrames.filter(frame => { return frame.parentLayer === selectedFrame.parentLayer; }).forEach(frame => { var start = frame.start; var end = frame.end; - if (!frameBounds.playheadStart || !frameBounds.playheadEnd) { frameBounds.playheadStart = start; frameBounds.playheadEnd = end; } - if (start < frameBounds.playheadStart) { frameBounds.playheadStart = start; } - if (end > frameBounds.playheadEnd) { frameBounds.playheadEnd = end; } - }); // Select all frames inside bounding box + }); + // Select all frames inside bounding box this.project.activeTimeline.getAllFrames().filter(frame => { return !frame.isSelected && frame.parentLayer === selectedFrame.parentLayer && frame.inRange(frameBounds.playheadStart, frameBounds.playheadEnd); }).forEach(frame => { this._selectedObjectsUUIDs.push(frame.uuid); }); } - }; /* * Copyright 2020 WICKLETS LLC @@ -53109,146 +52663,126 @@ Wick.Timeline = class extends Wick.Base { this._fillGapsMethod = "auto_extend"; this._frameForced = false; } - _serialize(args) { var data = super._serialize(args); - data.playheadPosition = this._playheadPosition; data.activeLayerIndex = this._activeLayerIndex; return data; } - _deserialize(data) { super._deserialize(data); - this._playheadPosition = data.playheadPosition; this._activeLayerIndex = data.activeLayerIndex; this._playing = true; } - get classname() { return 'Timeline'; } + /** * The layers that belong to this timeline. * @type {Wick.Layer} */ - - get layers() { return this.getChildren('Layer'); } + /** * The position of the playhead. Determines which frames are visible. * @type {number} */ - - get playheadPosition() { return this._playheadPosition; } - set playheadPosition(playheadPosition) { // Automatically clear selection when any playhead in the project moves if (this.project && this._playheadPosition !== playheadPosition && this.parentClip.isFocus) { this.project.selection.clear('Canvas'); this.project.resetTools(); } - this._playheadPosition = playheadPosition; - if (this._playheadPosition < 1) { this._playheadPosition = 1; - } // Automatically apply tween transforms on child frames when playhead moves - + } + // Automatically apply tween transforms on child frames when playhead moves this.activeFrames.forEach(frame => { frame.applyTweenTransforms(); frame.updateClipTimelinesForAnimationType(); }); } + /** * Forces timeline to move to the next frame. * @param {number} frame */ - - forceFrame(frame) { this.playheadPosition = frame; this._frameForced = true; this.makeTimelineInBounds(); } + /** * Returns true if the frame was forced previously. */ - - get frameForced() { return this._frameForced; } + /** * The index of the active layer. Determines which frame to draw onto. * @type {number} */ - - get activeLayerIndex() { return this._activeLayerIndex; } - set activeLayerIndex(activeLayerIndex) { this._activeLayerIndex = activeLayerIndex; } + /** * The total length of the timeline. * @type {number} */ - - get length() { var length = 0; this.layers.forEach(function (layer) { var layerLength = layer.length; - if (layerLength > length) { length = layerLength; } }); return length; } + /** * The active layer. * @type {Wick.Layer} */ - - get activeLayer() { return this.layers[this.activeLayerIndex]; } + /** * The active frames, determined by the playhead position. * @type {Wick.Frame[]} */ - - get activeFrames() { var frames = []; this.layers.forEach(layer => { var layerFrame = layer.activeFrame; - if (layerFrame) { frames.push(layerFrame); } }); return frames; } + /* * exports the project as an SVG file * @onError {function(message)} * @returns {string} - the SVG for the current view in string form (maybe this should be base64 or a blob or something) */ - - exportSVG(onError) { var svgOutput = paper.project.exportSVG({ asString: true, @@ -53256,24 +52790,21 @@ Wick.Timeline = class extends Wick.Base { embedImages: true }); return svgOutput; - } //this.project.paper. + } + //this.project.paper. //paperGroup = new paper.Group - /** * The active frame, determined by the playhead position. * @type {Wick.Frame} */ - - get activeFrame() { return this.activeLayer && this.activeLayer.activeFrame; } + /** * All frames inside the timeline. * @type {Wick.Frame[]} */ - - get frames() { var frames = []; this.layers.forEach(layer => { @@ -53283,12 +52814,11 @@ Wick.Timeline = class extends Wick.Base { }); return frames; } + /** * All clips inside the timeline. * @type {Wick.Clip[]} */ - - get clips() { var clips = []; this.frames.forEach(frame => { @@ -53296,41 +52826,36 @@ Wick.Timeline = class extends Wick.Base { }); return clips; } + /** * Finds the frame with a given name. * @type {Wick.Frame|null} */ - - getFrameByName(name) { return this.frames.find(frame => { return frame.name === name; }) || null; } + /** * Add a frame to one of the layers on this timeline. If there is no layer where the frame wants to go, the frame will not be added. * @param {Wick.Frame} frame - the frame to add */ - - addFrame(frame) { if (frame.originalLayerIndex >= this.layers.length) return; - if (frame.originalLayerIndex === -1) { this.activeLayer.addFrame(frame); } else { this.layers[frame.originalLayerIndex].addFrame(frame); } } + /** * Adds a layer to the timeline. * @param {Wick.Layer} layer - The layer to add. */ - - addLayer(layer) { this.addChild(layer); - if (!layer.name) { if (this.layers.length > 1) { layer.name = "Layer " + this.layers.length; @@ -53339,60 +52864,54 @@ Wick.Timeline = class extends Wick.Base { } } } + /** * Adds a tween to a frame on this timeline. * @param {Wick.Tween} tween - the tween to add. */ - - addTween(tween) { if (tween.originalLayerIndex >= this.layers.length) return; - if (tween.originalLayerIndex === -1) { this.activeLayer.addTween(tween); } else { this.layers[tween.originalLayerIndex].addTween(tween); } } + /** * Remmoves a layer from the timeline. * @param {Wick.Layer} layer - The layer to remove. */ - - removeLayer(layer) { // You can't remove the last layer. if (this.layers.length <= 1) { return; - } // Activate the layer below the removed layer if we removed the active layer. - + } + // Activate the layer below the removed layer if we removed the active layer. if (this.activeLayerIndex === this.layers.length - 1) { this.activeLayerIndex--; } - this.removeChild(layer); } + /** * Moves a layer to a different position, inserting it before/after other layers if needed. * @param {Wick.Layer} layer - The layer to add. * @param {number} index - the new position to move the layer to. */ - - moveLayer(layer, index) { var layers = this.getChildren('Layer'); layers.splice(layers.indexOf(layer), 1); layers.splice(index, 0, layer); this._children = layers; } + /** * Gets the frames at the given playhead position. * @param {number} playheadPosition - the playhead position to search. * @returns {Wick.Frame[]} The frames at the playhead position. */ - - getFramesAtPlayheadPosition(playheadPosition) { var frames = []; this.layers.forEach(layer => { @@ -53401,17 +52920,15 @@ Wick.Timeline = class extends Wick.Base { }); return frames; } + /** * Get all frames in this timeline. * @param {boolean} recursive - If set to true, will also include the children of all child timelines. */ - - getAllFrames(recursive) { var allFrames = []; this.layers.forEach(layer => { allFrames = allFrames.concat(layer.frames); - if (recursive) { layer.frames.forEach(frame => { frame.clips.forEach(clip => { @@ -53422,6 +52939,7 @@ Wick.Timeline = class extends Wick.Base { }); return allFrames; } + /** * Gets all frames in the layer that are between the two given playhead positions and layer indices. * @param {number} playheadPositionStart - The start of the horizontal range to search @@ -53430,8 +52948,6 @@ Wick.Timeline = class extends Wick.Base { * @param {number} layerIndexEnd - The end of the vertical range to search * @return {Wick.Frame[]} The frames in the given range. */ - - getFramesInRange(playheadPositionStart, playheadPositionEnd, layerIndexStart, layerIndexEnd) { var framesInRange = []; this.layers.filter(layer => { @@ -53441,11 +52957,10 @@ Wick.Timeline = class extends Wick.Base { }); return framesInRange; } + /** * Advances the timeline one frame forwards. Loops back to beginning if the end is reached. */ - - advance() { if (this._playing) { this.playheadPosition++; @@ -53453,93 +52968,80 @@ Wick.Timeline = class extends Wick.Base { this.makeTimelineInBounds(); } } + /** * Ensures playhead position is in bounds. */ - - makeTimelineInBounds() { if (this.playheadPosition > this.length) { this.playheadPosition = 1; } } + /** * Makes the timeline advance automatically during ticks. */ - - play() { this._playing = true; } + /** * Stops the timeline from advancing during ticks. */ - - stop() { this._playing = false; } + /** * Stops the timeline and moves to a given frame number or name. * @param {string|number} frame - A playhead position or name of a frame to move to. */ - - gotoAndStop(frame) { this.stop(); this.gotoFrame(frame); } + /** * Plays the timeline and moves to a given frame number or name. * @param {string|number} frame - A playhead position or name of a frame to move to. */ - - gotoAndPlay(frame) { this.play(); this.gotoFrame(frame); } + /** * Moves the timeline forward one frame. Loops back to 1 if gotoNextFrame moves the playhead past the past frame. */ - - gotoNextFrame() { // Loop back to beginning if gotoNextFrame goes past the last frame var nextFramePlayheadPosition = this.playheadPosition + 1; - if (nextFramePlayheadPosition > this.length) { nextFramePlayheadPosition = 1; } - this.gotoFrame(nextFramePlayheadPosition); } + /** * Moves the timeline backwards one frame. Loops to the last frame if gotoPrevFrame moves the playhead before the first frame. */ - - gotoPrevFrame() { var prevFramePlayheadPosition = this.playheadPosition - 1; - if (prevFramePlayheadPosition <= 0) { prevFramePlayheadPosition = this.length; } - this.gotoFrame(prevFramePlayheadPosition); } + /** * Moves the playhead to a given frame number or name. * @param {string|number} frame - A playhead position or name of a frame to move to. */ - - gotoFrame(frame) { if (typeof frame === 'string') { var namedFrame = this.frames.find(seekframe => { return seekframe.identifier === frame && !seekframe.onScreen; }); - if (namedFrame) { this.forceFrame(namedFrame.start); } @@ -53549,16 +53051,14 @@ Wick.Timeline = class extends Wick.Base { throw new Error('gotoFrame: Invalid argument: ' + frame); } } + /** * The method to use to fill gaps in-beteen frames. Options: "blank_frames" or "auto_extend" (see Wick.Layer.resolveGaps) * @type {string} */ - - get fillGapsMethod() { return this._fillGapsMethod; } - set fillGapsMethod(fillGapsMethod) { if (fillGapsMethod === 'blank_frames' || fillGapsMethod === 'auto_extend') { this._fillGapsMethod = fillGapsMethod; @@ -53567,29 +53067,26 @@ Wick.Timeline = class extends Wick.Base { console.warn('Valid fillGapsMethod: "blank_frames", "auto_extend"'); } } + /** * Check if frame gap fixing should be deferred until later. Read only. * @type {boolean} */ - - get waitToFillFrameGaps() { return this._waitToFillFrameGaps; } + /** * Disables frame gap filling until resolveFrameGaps is called again. */ - - deferFrameGapResolve() { this._waitToFillFrameGaps = true; } + /** * Fill in all gaps between frames in all layers in this timeline. * @param {Wick.Frame[]} newOrModifiedFrames - The frames that should not be affected by the gap fill by being extended or shrunk. */ - - resolveFrameGaps(newOrModifiedFrames) { if (!newOrModifiedFrames) newOrModifiedFrames = []; this._waitToFillFrameGaps = false; @@ -53599,12 +53096,11 @@ Wick.Timeline = class extends Wick.Base { })); }); } + /** * Prevents frames from overlapping each other by removing pieces of frames that are touching. * @param {Wick.Frame[]} newOrModifiedFrames - the frames that should take precedence when determining which frames should get "eaten". */ - - resolveFrameOverlap(frames) { this.layers.forEach(layer => { layer.resolveOverlap(frames.filter(frame => { @@ -53612,7 +53108,6 @@ Wick.Timeline = class extends Wick.Base { })); }); } - }; /* * Copyright 2020 WICKLETS LLC @@ -53640,7 +53135,6 @@ Wick.Tween = class extends Wick.Base { static get VALID_EASING_TYPES() { return ['none', 'in', 'out', 'in-out']; } - static _calculateTimeValue(tweenA, tweenB, playheadPosition) { var tweenAPlayhead = tweenA.playheadPosition; var tweenBPlayhead = tweenB.playheadPosition; @@ -53648,14 +53142,13 @@ Wick.Tween = class extends Wick.Base { var t = (playheadPosition - tweenAPlayhead) / dist; return t; } + /** * Create a tween * @param {number} playheadPosition - the playhead position relative to the frame that the tween belongs to * @param {Wick.Transform} transformation - the transformation this tween will apply to child objects * @param {number} fullRotations - the number of rotations to add to the tween's transformation */ - - constructor(args) { if (!args) args = {}; super(args); @@ -53665,31 +53158,28 @@ Wick.Tween = class extends Wick.Base { this.easingType = args.easingType || 'none'; this._originalLayerIndex = -1; } + /** * Create a tween by interpolating two existing tweens. * @param {Wick.Tween} tweenA - The first tween * @param {Wick.Tween} tweenB - The second tween * @param {Number} playheadPosition - The point between the two tweens to use to interpolate */ - - static interpolate(tweenA, tweenB, playheadPosition) { - var interpTween = new Wick.Tween(); // Calculate value (0.0-1.0) to pass to tweening function - - var t = Wick.Tween._calculateTimeValue(tweenA, tweenB, playheadPosition); // Interpolate every transformation attribute using the t value + var interpTween = new Wick.Tween(); + // Calculate value (0.0-1.0) to pass to tweening function + var t = Wick.Tween._calculateTimeValue(tweenA, tweenB, playheadPosition); + // Interpolate every transformation attribute using the t value ["x", "y", "scaleX", "scaleY", "rotation", "opacity"].forEach(propName => { var tweenFn = tweenA._getTweenFunction(); - var tt = tweenFn(t); var valA = tweenA.transformation[propName]; var valB = tweenB.transformation[propName]; - if (propName === 'rotation') { // Constrain rotation values to range of -180 to 180 // (Disabled for now - a bug in paper.js clamps these for us) - /*while(valA < -180) valA += 360; while(valB < -180) valB += 360; while(valA > 180) valA -= 360; @@ -53697,20 +53187,16 @@ Wick.Tween = class extends Wick.Base { // Convert full rotations to 360 degree amounts valB += tweenA.fullRotations * 360; } - interpTween.transformation[propName] = lerp(valA, valB, tt); }); interpTween.playheadPosition = playheadPosition; return interpTween; } - get classname() { return 'Tween'; } - _serialize(args) { var data = super._serialize(args); - data.playheadPosition = this.playheadPosition; data.transformation = this._transformation.values; data.fullRotations = this.fullRotations; @@ -53718,122 +53204,107 @@ Wick.Tween = class extends Wick.Base { data.originalLayerIndex = this.layerIndex !== -1 ? this.layerIndex : this._originalLayerIndex; return data; } - _deserialize(data) { super._deserialize(data); - this.playheadPosition = data.playheadPosition; this._transformation = new Wick.Transformation(data.transformation); this.fullRotations = data.fullRotations; this.easingType = data.easingType; this._originalLayerIndex = data.originalLayerIndex; } + /** * The playhead position of the tween. * @type {number} */ - - get playheadPosition() { return this._playheadPosition; } - set playheadPosition(playheadPosition) { this._playheadPosition = playheadPosition; } + /** * The transformation representing the position, rotation and other elements of the tween. * @type {object} */ - - get transformation() { return this._transformation; } - set transformation(transformation) { this._transformation = transformation; } + /** * The type of interpolation to use for easing. * @type {string} */ - - get easingType() { return this._easingType; } - set easingType(easingType) { if (Wick.Tween.VALID_EASING_TYPES.indexOf(easingType) === -1) { console.warn('Invalid easingType. Valid easingTypes: '); console.warn(Wick.Tween.VALID_EASING_TYPES); return; } - this._easingType = easingType; } + /** * Remove this tween from its parent frame. */ - - remove() { this.parent.removeTween(this); } + /** * Set the transformation of a clip to this tween's transformation. * @param {Wick.Clip} clip - the clip to apply the tween transforms to. */ - - applyTransformsToClip(clip) { clip.transformation = this.transformation.copy(); } + /** * The tween that comes after this tween in the parent frame. * @returns {Wick.Tween} */ - - getNextTween() { if (!this.parentFrame) return null; var frontTween = this.parentFrame.seekTweenInFront(this.playheadPosition + 1); return frontTween; } + /** * Prevents tweens from existing outside of the frame's length. Call this after changing the length of the parent frame. */ - - restrictToFrameSize() { - var playheadPosition = this.playheadPosition; // Remove tween if playheadPosition is out of bounds + var playheadPosition = this.playheadPosition; + // Remove tween if playheadPosition is out of bounds if (playheadPosition < 1 || playheadPosition > this.parentFrame.length) { this.remove(); } } + /** * The index of the parent layer of this tween. * @type {number} */ - - get layerIndex() { return this.parentLayer ? this.parentLayer.index : -1; } + /** * The index of the layer that this tween last belonged to. Used when copying and pasting tweens. * @type {number} */ - - get originalLayerIndex() { return this._originalLayerIndex; } - /* retrieve Tween.js easing functions by name */ - + /* retrieve Tween.js easing functions by name */ _getTweenFunction() { return { 'none': TWEEN.Easing.Linear.None, @@ -53842,7 +53313,6 @@ Wick.Tween = class extends Wick.Base { 'in-out': TWEEN.Easing.Quadratic.InOut }[this.easingType]; } - }; /* * Copyright 2020 WICKLETS LLC @@ -53879,7 +53349,6 @@ Wick.Path = class extends Wick.Base { this._fontWeight = 400; this._isPlaceholder = args.isPlaceholder; this._originalStyle = null; - if (args.path) { this.json = args.path.exportJSON({ asString: false @@ -53893,20 +53362,17 @@ Wick.Path = class extends Wick.Base { asString: false }); } - this.needReimport = true; } + /** * Create a path containing an image from an ImageAsset. * @param {Wick.ImageAsset} asset - The asset from which the image src will be loaded from * @param {Function} callback - A function that will be called when the image is done loading. */ - - static createImagePath(asset, callback) { var img = new Image(); img.src = asset.src; - img.onload = () => { var raster = new paper.Raster(img); raster.remove(); @@ -53917,12 +53383,11 @@ Wick.Path = class extends Wick.Base { callback(path); }; } + /** * Create a path (synchronously) containing an image from an ImageAsset. * @param {Wick.ImageAsset} asset - The asset from which the image src will be loaded from */ - - static createImagePathSync(asset) { var raster = new paper.Raster(asset.src); raster.remove(); @@ -53932,17 +53397,15 @@ Wick.Path = class extends Wick.Base { }); return path; } - get classname() { return 'Path'; } - _serialize(args) { var data = super._serialize(args); - data.json = this.json; - delete data.json[1].data; // optimization: replace dataurls with asset uuids + delete data.json[1].data; + // optimization: replace dataurls with asset uuids if (data.json[0] === 'Raster' && data.json[1].source.startsWith('data:')) { if (!this.project) { console.warn('Could not replace raster image source with asset UUID, path does not belong to a project.'); @@ -53954,35 +53417,30 @@ Wick.Path = class extends Wick.Base { }); } } - data.fontStyle = this._fontStyle; data.fontWeight = this._fontWeight; data.isPlaceholder = this._isPlaceholder; return data; } - _deserialize(data) { super._deserialize(data); - this.json = data.json; this._fontStyle = data.fontStyle || 'normal'; this._fontWeight = data.fontWeight || 400; this._isPlaceholder = data.isPlaceholder; } + /** * Determines if this Path is visible in the project. */ - - get onScreen() { return this.parent.onScreen; } + /** * The type of path that this path is. Can be 'path', 'text', or 'image' * @returns {string} */ - - get pathType() { if (this.view.item instanceof paper.TextItem) { return 'text'; @@ -53992,27 +53450,24 @@ Wick.Path = class extends Wick.Base { return 'path'; } } + /** * Path data exported from paper.js using exportJSON({asString:false}). * @type {object} */ - - get json() { return this._json; } - set json(json) { this._json = json; this.needReimport = true; this.view.render(); } + /** * The bounding box of the path. * @type {object} */ - - get bounds() { var paperBounds = this.view.item.bounds; return { @@ -54024,291 +53479,252 @@ Wick.Path = class extends Wick.Base { height: paperBounds.height }; } + /** * The position of the path. * @type {number} */ - - get x() { return this.view.item.position.x; } - set x(x) { this.view.item.position.x = x; this.updateJSON(); } + /** * The position of the path. * @type {number} */ - - get y() { return this.view.item.position.y; } - set y(y) { this.view.item.position.y = y; this.updateJSON(); } + /** * The fill color of the path. * @type {paper.Color} */ - - get fillColor() { return this.view.item.fillColor || new paper.Color(); } - set fillColor(fillColor) { this.view.item.fillColor = fillColor; this.updateJSON(); } + /** * The stroke color of the path. * @type {paper.Color} */ - - get strokeColor() { return this.view.item.strokeColor || new paper.Color(); } - set strokeColor(strokeColor) { this.view.item.strokeColor = strokeColor; this.updateJSON(); } + /** * The stroke width of the path. * @type {number} */ - - get strokeWidth() { return this.view.item.strokeWidth; } - set strokeWidth(strokeWidth) { this.view.item.strokeWidth = strokeWidth; this.updateJSON(); } + /** * The opacity of the path. * @type {number} */ - - get opacity() { if (this.view.item.opacity === undefined || this.view.item.opacity === null) { return 1.0; } - return this.view.item.opacity; } - set opacity(opacity) { this.view.item.opacity = opacity; this.updateJSON(); } + /** * The font family of the path. * @type {string} */ - - get fontFamily() { return this.view.item.fontFamily; } - set fontFamily(fontFamily) { this.view.item.fontFamily = fontFamily; this.fontWeight = 400; this.fontStyle = 'normal'; this.updateJSON(); } + /** * The font size of the path. * @type {number} */ - - get fontSize() { return this.view.item.fontSize; } - set fontSize(fontSize) { this.view.item.fontSize = fontSize; this.view.item.leading = fontSize * 1.2; this.updateJSON(); } + /** * The font weight of the path. * @type {number} */ - - get fontWeight() { return this._fontWeight; } - set fontWeight(fontWeight) { if (typeof fontWeight === 'string') { console.error('fontWeight must be a number.'); return; } - this._fontWeight = fontWeight; this.updateJSON(); } + /** * The font style of the path ('italic' or 'oblique'). * @type {string} */ - - get fontStyle() { return this._fontStyle; } - set fontStyle(fontStyle) { this._fontStyle = fontStyle; this.updateJSON(); } + /** * The original style of the path (used to recover the path's style if it was changed by a custom onion skin style) * @type {object} */ - - get originalStyle() { return this._originalStyle; } - set originalStyle(originalStyle) { this._originalStyle = originalStyle; } + /** * The content of the text. * @type {string} */ - - get textContent() { return this.view.item.content; } - set textContent(textContent) { this.view.item.content = textContent; } + /** * Update the JSON of the path based on the path on the view. */ - - updateJSON() { this.json = this.view.exportJSON(); } + /** * API function to change the textContent of dynamic text paths. */ - - setText(newTextContent) { this.textContent = newTextContent; } + /** * Check if this path is a dynamic text object. * @type {boolean} */ - - get isDynamicText() { return this.pathType === 'text' && this.identifier !== null; } + /** * The image asset that this path uses, if this path is a Raster path. * @returns {Wick.Asset[]} */ //should this also return the SVGAsset if the path is loaded from an SVGAsset - - getLinkedAssets() { var linkedAssets = []; var data = this.serialize(); // just need the asset uuid... - if (data.json[0] === 'Raster') { var uuid = data.json[1].source.split(':')[1]; linkedAssets.push(this.project.getAssetByUUID(uuid)); } - return linkedAssets; } + /** * Removes this path from its parent frame. */ - - remove() { this.parentFrame.removePath(this); } + /** * Creates a new path using boolean unite on multiple paths. The resulting path will use the fillColor, strokeWidth, and strokeColor of the first path in the array. * @param {Wick.Path[]} paths - an array containing the paths to process. * @returns {Wick.Path} The path resulting from the boolean unite. */ - - static unite(paths) { return Wick.Path.booleanOp(paths, 'unite'); } + /** * Creates a new path using boolean subtration on multiple paths. The resulting path will use the fillColor, strokeWidth, and strokeColor of the first path in the array. * @param {Wick.Path[]} paths - an array containing the paths to process. * @returns {Wick.Path} The path resulting from the boolean subtraction. */ - - static subtract(paths) { return Wick.Path.booleanOp(paths, 'subtract'); } + /** * Creates a new path using boolean intersection on multiple paths. The resulting path will use the fillColor, strokeWidth, and strokeColor of the first path in the array. * @param {Wick.Path[]} paths - an array containing the paths to process. * @returns {Wick.Path} The path resulting from the boolean intersection. */ - - static intersect(paths) { return Wick.Path.booleanOp(paths, 'intersect'); } + /** * Perform a paper.js boolean operation on a list of paths. * @param {Wick.Path[]} paths - a list of paths to perform the boolean operation on. * @param {string} booleanOpName - the name of the boolean operation to perform. Currently supports "unite", "subtract", and "intersect" */ - - static booleanOp(paths, booleanOpName) { if (!booleanOpName) { console.error('Wick.Path.booleanOp: booleanOpName is required'); } - if (booleanOpName !== 'unite' && booleanOpName !== 'subtract' && booleanOpName !== 'intersect') { console.error('Wick.Path.booleanOp: unsupported booleanOpName: ' + booleanOpName); } - if (!paths || paths.length === 0) { console.error('Wick.Path.booleanOp: a non-empty list of paths is required'); - } // Single path? Nothing to do. - + } + // Single path? Nothing to do. if (paths.length === 1) { return paths[0]; - } // Get paper.js path objects - + } + // Get paper.js path objects paths = paths.map(path => { return path.view.item; }); @@ -54327,21 +53743,18 @@ Wick.Path = class extends Wick.Base { }); return resultWickPath; } + /** * Converts a stroke into fill. Only works with paths that have a strokeWidth and strokeColor, and have no fillColor. Does nothing otherwise. * @returns {Wick.Path} A flattened version of this path. Can be null if the path cannot be flattened. */ - - flatten() { if (this.fillColor || !this.strokeColor || !this.strokeWidth) { return null; } - if (!(this instanceof paper.Path)) { return null; } - var flatPath = new Wick.Path({ json: this.view.item.flatten().exportJSON({ asString: false @@ -54350,20 +53763,17 @@ Wick.Path = class extends Wick.Base { flatPath.fillColor = this.strokeColor; return flatPath; } + /** * Is this path used as a placeholder for preventing empty clips? * @type {bool} */ - - set isPlaceholder(isPlaceholder) { this._isPlaceholder = isPlaceholder; } - get isPlaceholder() { return this._isPlaceholder; } - }; /* * Copyright 2020 WICKLETS LLC @@ -54383,6 +53793,7 @@ Wick.Path = class extends Wick.Base { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.Asset = class extends Wick.Base { /** * Creates a new Wick Asset. @@ -54393,46 +53804,40 @@ Wick.Asset = class extends Wick.Base { super(args); this.name = args.name; } - _serialize(args) { var data = super._serialize(args); - data.name = this.name; return data; } - _deserialize(data) { super._deserialize(data); - this.name = data.name; } + /** * A list of all objects using this asset. */ - - - getInstances() {// Implemented by subclasses + getInstances() { + // Implemented by subclasses } + /** * Check if there are any objects in the project that use this asset. * @returns {boolean} */ - - - hasInstances() {// Implemented by sublasses + hasInstances() { + // Implemented by sublasses } + /** * Remove all instances of this asset from the project. (Implemented by ClipAsset, ImageAsset, and SoundAsset) */ - - - removeAllInstances() {// Implemented by sublasses + removeAllInstances() { + // Implemented by sublasses } - get classname() { return 'Asset'; } - }; /* * Copyright 2020 WICKLETS LLC @@ -54452,6 +53857,7 @@ Wick.Asset = class extends Wick.Base { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.FileAsset = class extends Wick.Asset { /** * Returns all valid MIME types for files which can be converted to Wick Assets. @@ -54466,13 +53872,12 @@ Wick.FileAsset = class extends Wick.Asset { let gifTypes = Wick.GIFAsset.getValidMIMETypes(); return imageTypes.concat(soundTypes).concat(fontTypes).concat(clipTypes).concat(svgTypes).concat(gifTypes); } + /** * Returns all valid extensions types for files which can be attempted to be * converted to Wick Assets. * @return {string[]} Array of strings representing extensions. */ - - static getValidExtensions() { let imageExtensions = Wick.ImageAsset.getValidExtensions(); let soundExtensions = Wick.SoundAsset.getValidExtensions(); @@ -54482,13 +53887,12 @@ Wick.FileAsset = class extends Wick.Asset { let gifExtensions = Wick.GIFAsset.getValidExtensions(); return imageExtensions.concat(soundExtensions).concat(fontExtensions).concat(clipExtensions).concat(svgExtensions).concat(gifExtensions); } + /** * Create a new FileAsset. * @param {string} filename - the filename of the file being used as this asset's source. * @param {string} src - a base64 string containing the source for this asset. */ - - constructor(args) { if (!args) args = {}; args.name = args.filename; @@ -54498,48 +53902,38 @@ Wick.FileAsset = class extends Wick.Asset { this.filename = args.filename; this.src = args.src; } - _serialize(args) { var data = super._serialize(args); - data.filename = this.filename; data.MIMEType = this.MIMEType; data.fileExtension = this.fileExtension; - if (args && args.includeOriginalSource) { data.originalSource = this.src; } - return data; } - _deserialize(data) { super._deserialize(data); - this.filename = data.filename; this.MIMEType = data.MIMEType; this.fileExtension = data.fileExtension; - if (data.originalSource) { this.src = data.originalSource; } } - get classname() { return 'FileAsset'; } + /** * The source of the data of the asset, in base64. Returns null if the file is not found. * @type {string} */ - - get src() { let file = Wick.FileCache.getFile(this.uuid); if (file) return file.src; return null; } - set src(src) { if (src) { Wick.FileCache.addFile(src, this.uuid); @@ -54547,36 +53941,30 @@ Wick.FileAsset = class extends Wick.Asset { this.MIMEType = this._MIMETypeOfString(src); } } + /** * Loads data about the file into the asset. */ - - load(callback) { callback(); } + /** * Copies the FileAsset and also copies the src in FileCache. * @return {Wick.FileAsset} */ - - copy() { var copy = super.copy(); copy.src = this.src; return copy; } - _MIMETypeOfString(string) { return string.split(':')[1].split(',')[0].split(';')[0]; } - _fileExtensionOfString(string) { var MIMEType = this._MIMETypeOfString(string); - return MIMEType && MIMEType.split('/')[1]; } - }; /* * Copyright 2020 WICKLETS LLC @@ -54596,6 +53984,7 @@ Wick.FileAsset = class extends Wick.Asset { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.FontAsset = class extends Wick.FileAsset { /** * Valid MIME types for font assets. @@ -54604,63 +53993,53 @@ Wick.FontAsset = class extends Wick.FileAsset { static getValidMIMETypes() { return ['font/ttf', 'application/x-font-ttf', 'application/x-font-truetype']; } + /** * Valid extensions for font assets. * @returns {string[]} Array of strings representing extensions. */ - - static getValidExtensions() { return ['.ttf']; } + /** * The default font to use if a font couldn't load, or if a FontAsset was deleted * @type {string} */ - - static get MISSING_FONT_DEFAULT() { return 'Helvetica, Arial, sans-serif'; } + /** * Create a new FontAsset. * @param {object} args - Asset constructor args. see constructor for Wick.Asset */ - - constructor(args) { super(args); } - _serialize(args) { var data = super._serialize(args); - return data; } - _deserialize(data) { super._deserialize(data); } - get classname() { return 'FontAsset'; } + /** * Loads the font into the window. * @param {function} callback - function to call when the font is done being loaded. */ - - load(callback) { var fontDataArraybuffer = Base64ArrayBuffer.decode(this.src.split(',')[1]); var fontFamily = this.fontFamily; - if (!fontFamily) { console.error('FontAsset: Could not get fontFamily from filename.'); } else if (fontFamily === "") { console.error('FontAsset: fontfamily not found. Showing as "".'); } - var font = new FontFace(fontFamily, fontDataArraybuffer); font.load().then(loaded_face => { document.fonts.add(loaded_face); @@ -54671,12 +54050,11 @@ Wick.FontAsset = class extends Wick.FileAsset { callback(); // Make the callback so that the page doesn't freeze. }); } + /** * A list of Wick Paths that use this font as their fontFamily. * @returns {Wick.Path[]} */ - - getInstances() { var paths = []; this.project.getAllFrames().forEach(frame => { @@ -54688,35 +54066,31 @@ Wick.FontAsset = class extends Wick.FileAsset { }); return paths; } + /** * Check if there are any objects in the project that use this asset. * @returns {boolean} */ - - hasInstances() { return this.getInstances().length > 0; } + /** * Finds all PointText paths using this font as their fontFamily and replaces that font with a default font. */ - - removeAllInstances() { this.getInstances().forEach(path => { path.fontFamily = Wick.FontAsset.MISSING_FONT_DEFAULT; }); } + /** * The name of the font that this FontAsset represents. * @type {string} */ - - get fontFamily() { return this.filename.split('.')[0]; } - }; /* * Copyright 2020 WICKLETS LLC @@ -54736,6 +54110,7 @@ Wick.FontAsset = class extends Wick.FileAsset { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.ImageAsset = class extends Wick.FileAsset { /** * Valid MIME types for image assets. @@ -54746,48 +54121,40 @@ Wick.ImageAsset = class extends Wick.FileAsset { let pngTypes = ['image/png']; return jpgTypes.concat(pngTypes); } + /** * Valid extensions for image assets. * @returns {string[]} Array of strings representing extensions. */ - - static getValidExtensions() { return ['.jpeg', '.jpg', '.png']; } + /** * Create a new ImageAsset. * @param {object} args - Asset constructor args. see constructor for Wick.Asset */ - - constructor(args) { super(args); this.gifAssetUUID = null; } - _serialize(args) { var data = super._serialize(args); - data.gifAssetUUID = this.gifAssetUUID; return data; } - _deserialize(data) { super._deserialize(data); - this.gifAssetUUID = data.gifAssetUUID; } - get classname() { return 'ImageAsset'; } + /** * A list of Wick Paths that use this image as their image source. * @returns {Wick.Path[]} */ - - getInstances() { var paths = []; this.project.getAllFrames().forEach(frame => { @@ -54799,69 +54166,61 @@ Wick.ImageAsset = class extends Wick.FileAsset { }); return paths; } + /** * Check if there are any objects in the project that use this asset. * @returns {boolean} */ - - hasInstances() { return this.getInstances().length > 0; } + /** * Removes all paths using this asset as their image source from the project. * @returns {boolean} */ - - removeAllInstances() { this.getInstances().forEach(path => { path.remove(); }); } + /** * Load data in the asset * @param {function} callback - function to call when the data is done being loaded. */ - - load(callback) { // Try to get paper.js to cache the image src. var img = new Image(); img.src = this.src; - img.onload = () => { var raster = new paper.Raster(img); raster.remove(); callback(); }; - img.onerror = () => { this.project.errorOccured("Error loading image " + this.filename + ". Check that this is loaded properly."); callback(); }; } + /** * Creates a new Wick Path that uses this asset's image data as it's image source. * @param {function} callback - called when the path is done loading. */ - - createInstance(callback) { Wick.Path.createImagePath(this, path => { callback(path); }); } + /** * Is this image asset part of a GIF? (if this is set to true, this asset won't appear in the asset library GUI) * @type {boolean} */ - - get isGifImage() { return this.gifAssetUUID; } - }; /* * Copyright 2020 WICKLETS LLC @@ -54881,6 +54240,7 @@ Wick.ImageAsset = class extends Wick.FileAsset { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.ClipAsset = class extends Wick.FileAsset { /** * Returns all valid MIME types for files which can be converted to ClipAssets. @@ -54889,29 +54249,26 @@ Wick.ClipAsset = class extends Wick.FileAsset { static getValidMIMETypes() { return ['application/json', 'application/octet-stream']; } + /** * Returns all valid extensions types for files which can be attempted to be * converted to ClipAssets. * @return {string[]} Array of strings representing extensions. */ - - static getValidExtensions() { return ['.wickobj']; } + /** * Creates a ClipAsset from the data of a given Clip. * @param {Wick.Clip} - the clip to use as a source * @param {function} callback - */ - - static fromClip(clip, project, callback) { project.addObject(clip); Wick.WickObjectFile.toWickObjectFile(clip, 'blob', file => { // Convert blob to dataURL var a = new FileReader(); - a.onload = e => { // Create ClipAsset var clipAsset = new Wick.ClipAsset({ @@ -54921,39 +54278,32 @@ Wick.ClipAsset = class extends Wick.FileAsset { clip.remove(); callback(clipAsset); }; - a.readAsDataURL(file); }); } + /** * Create a new ClipAsset. * @param {object} args */ - - constructor(args) { super(args); } - _serialize(args) { var data = super._serialize(args); - return data; } - _deserialize(data) { super._deserialize(data); } - get classname() { return 'ClipAsset'; } + /** * A list of Wick Clips that use this ClipAsset as their source. * @returns {Wick.Clip[]} */ - - getInstances() { var clips = []; this.project.getAllFrames().forEach(frame => { @@ -54965,68 +54315,61 @@ Wick.ClipAsset = class extends Wick.FileAsset { }); return clips; } + /** * Check if there are any objects in the project that use this asset. * @returns {boolean} */ - - hasInstances() { return this.getInstances().length > 0; } + /** * Removes all Clips using this asset as their source from the project. * @returns {boolean} */ - - removeAllInstances() { this.getInstances().forEach(instance => { instance.remove(); - }); // Also remove any ImageAssets that are part of this clip, and are GIF frames + }); + // Also remove any ImageAssets that are part of this clip, and are GIF frames this.project.getAllFrames().forEach(frame => { frame.paths.forEach(path => { var images = path.getLinkedAssets(); - if (images.length > 0 && images[0].gifAssetUUID === this.uuid) { images[0].remove(); } }); }); } + /** * Load data in the asset * @param {function} callback - function to call when the data is done being loaded. */ - - load(callback) { // We don't need to do anything here, the data for ClipAssets is just json callback(); } + /** * Creates a new Wick Clip that uses this asset's data. * @param {function} callback - called when the Clip is done loading. */ - - createInstance(callback, project) { if (!callback) { console.warn("Cannot create clip instance without callback."); } - if (!project) { console.warn("Cannot create clip instance without project reference."); } - Wick.WickObjectFile.fromWickObjectFile(this.src, data => { var clip = Wick.Base.import(data, project).copy(); clip.assetSourceUUID = this.uuid; callback(clip); }); } - }; /* * Copyright 2020 WICKLETS LLC @@ -55046,6 +54389,7 @@ Wick.ClipAsset = class extends Wick.FileAsset { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.GIFAsset = class extends Wick.ClipAsset { /** * Returns all valid MIME types for files which can be converted to GIFAssets. @@ -55054,28 +54398,25 @@ Wick.GIFAsset = class extends Wick.ClipAsset { static getValidMIMETypes() { return ['image/gif']; } + /** * Returns all valid extensions types for files which can be attempted to be * converted to GIFAssets. * @return {string[]} Array of strings representing extensions. */ - - static getValidExtensions() { return ['.gif']; } + /** * Create a new GIFAsset from a series of images. * @param {Wick.ImageAsset} images - The ImageAssets, in order of where they will appear in the timeline, which are used to create a ClipAsset * @param {function} callback - Fuction to be called when the asset is done being created */ - - static fromImages(images, project, callback) { var clip = new Wick.Clip(); clip.activeFrame.remove(); var imagesCreatedCount = 0; - var processNextImage = () => { images[imagesCreatedCount].createInstance(imagePath => { // Create a frame for every image @@ -55083,10 +54424,10 @@ Wick.GIFAsset = class extends Wick.ClipAsset { start: imagesCreatedCount + 1 }); frame.addPath(imagePath); - clip.activeLayer.addFrame(frame); // Check if all images have been created + clip.activeLayer.addFrame(frame); + // Check if all images have been created imagesCreatedCount++; - if (imagesCreatedCount === images.length) { Wick.ClipAsset.fromClip(clip, project, clipAsset => { // Attach a reference to the resulting clip to all images @@ -55101,72 +54442,61 @@ Wick.GIFAsset = class extends Wick.ClipAsset { } }); }; - processNextImage(); } + /** * Create a new GIFAsset. * @param {object} args - Asset args, see Wick.Asset constructor */ - - constructor(args) { super(args); } - _serialize(args) { var data = super._serialize(args); - return data; } - _deserialize(data) { super._deserialize(data); } - get classname() { return 'GIFAsset'; } + /** * A list of objects that use this asset as their source. * @returns {Wick.Clip[]} */ - - getInstances() { // Inherited from ClipAsset return super.getInstances(); } + /** * Check if there are any objects in the project that use this asset. * @returns {boolean} */ - - hasInstances() { // Inherited from ClipAsset return super.hasInstances(); } + /** * Removes all objects using this asset as their source from the project. * @returns {boolean} */ - - removeAllInstances() { // Inherited from ClipAsset super.removeAllInstances(); } + /** * Load data in the asset */ - - load(callback) { // We don't need to do anything here, the data for ClipAssets/GIFAssets is just json callback(); } - }; /* * Copyright 2020 WICKLETS LLC @@ -55186,6 +54516,7 @@ Wick.GIFAsset = class extends Wick.ClipAsset { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.SoundAsset = class extends Wick.FileAsset { /** * Returns valid MIME types for a Sound Asset. @@ -55197,39 +54528,34 @@ Wick.SoundAsset = class extends Wick.FileAsset { let wavTypes = ['audio/wave', 'audio/wav', 'audio/x-wav', 'audio/x-pn-wav']; return mp3Types.concat(oggTypes).concat(wavTypes); } + /** * Returns valid extensions for a sound asset. * @returns {string[]} Array of strings representing valid */ - - static getValidExtensions() { return ['.mp3', '.ogg', '.wav']; } + /** * Creates a new SoundAsset. * @param {object} args - Asset constructor args. see constructor for Wick.Asset */ - - constructor(args) { super(args); this._waveform = null; } - _serialize(args) { var data = super._serialize(args); - return data; } - _deserialize(data) { super._deserialize(data); } - get classname() { return 'SoundAsset'; } + /** * Plays this asset's sound. * @param {number} seekMS - the amount of time in milliseconds into the sound the sound should start at. @@ -55237,61 +54563,51 @@ Wick.SoundAsset = class extends Wick.FileAsset { * @param {boolean} loop - if set to true, the sound will loop * @return {number} The id of the sound instance that was played. */ - - play(options) { if (!options) options = {}; if (options.seekMS === undefined) options.seekMS = 0; if (options.volume === undefined) options.volume = 1.0; - if (options.loop === undefined) options.loop = false; // don't do anything if the project is muted... + if (options.loop === undefined) options.loop = false; + // don't do anything if the project is muted... if (this.project.muted) { return; } - var id = this._howl.play(); - this._howl.seek(options.seekMS / 1000, id); - this._howl.volume(options.volume, id); - this._howl.loop(options.loop, id); - return id; } + /** * Stops this asset's sound. * @param {number} id - (optional) the ID of the instance to stop. If ID is not given, every instance of this sound will stop. */ - - stop(id) { // Howl instance was never created, sound has never played yet, so do nothing if (!this._howl) { return; } - if (id === undefined) { this._howl.stop(); } else { this._howl.stop(id); } } + /** * The length of the sound in seconds * @type {number} */ - - get duration() { return this._howl.duration(); } + /** * A list of frames that use this sound. * @returns {Wick.Frame[]} */ - - getInstances() { var frames = []; this.project.getAllFrames().forEach(frame => { @@ -55301,31 +54617,28 @@ Wick.SoundAsset = class extends Wick.FileAsset { }); return frames; } + /** * Check if there are any objects in the project that use this asset. * @returns {boolean} */ - - hasInstances() { return this.getInstances().length > 0; } + /** * Remove the sound from any frames in the project that use this asset as their sound. */ - - removeAllInstances() { this.getInstances().forEach(frame => { frame.removeSound(); }); } + /** * Loads data about the sound into the asset. * @param {function} callback - function to call when the data is done being loaded. */ - - load(callback) { this._generateWaveform(() => { this._waitForHowlLoad(() => { @@ -55333,16 +54646,14 @@ Wick.SoundAsset = class extends Wick.FileAsset { }); }); } + /** * Image of the waveform of this sound. * @type {Image} */ - - get waveform() { return this._waveform; } - get _howl() { // Lazily create howler instance if (!this._howlInstance) { @@ -55353,10 +54664,8 @@ Wick.SoundAsset = class extends Wick.FileAsset { src: [srcFixed] }); } - return this._howlInstance; } - _waitForHowlLoad(callback) { if (this._howl.state() === 'loaded') { callback(); @@ -55366,29 +54675,24 @@ Wick.SoundAsset = class extends Wick.FileAsset { }); } } - _generateWaveform(callback) { if (this._waveform) { callback(); return; } - var soundSrc = this.src; if (!soundSrc) console.log("error", this, soundSrc); var scwf = new SCWF(); scwf.generate(soundSrc, { onComplete: (png, pixels) => { this._waveform = new Image(); - this._waveform.onload = () => { callback(); }; - this._waveform.src = png; } }); } - }; /* * Copyright 2020 WICKLETS LLC @@ -55408,6 +54712,7 @@ Wick.SoundAsset = class extends Wick.FileAsset { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.SVGAsset = class extends Wick.FileAsset { /** * Returns all valid MIME types for files which can be converted to SVGAssets. @@ -55416,82 +54721,72 @@ Wick.SVGAsset = class extends Wick.FileAsset { static getValidMIMETypes() { return ['image/svg+xml']; } + /** * Returns all valid extensions types for files which can be attempted to be * converted to SVGAssets. * @return {string[]} Array of strings representing extensions. */ - - static getValidExtensions() { return ['.svg']; } + /** * Create a new SVGAsset. * @param {object} args */ - - constructor(args) { super(args); } - _serialize(args) { var data = super._serialize(args); - return data; } - _deserialize(data) { super._deserialize(data); } - get classname() { return 'SVGAsset'; } + /** * A list of Wick Paths, Clips and Layers that use this SVGAsset as their image source. * I think this should return Assets not Paths * @returns {Wick.Path[]} */ - - getInstances() { return []; // TODO } + /** * Check if there are any objects in the project that use this asset. * @returns {boolean} */ - - hasInstances() { return false; } + /** * Removes all Items using this asset as their source from the project. * @returns {boolean} */ - - - removeAllInstances() {// TODO + removeAllInstances() { + // TODO } + /** * Load data in the asset */ - - load(callback) { // We don't need to do anything here, the data for SVGAssets is just SVG callback(); } + /** * Walks through the items tree creating the apprptiate wick object for each node* * @param {paper.Item} item - called when the Path is done loading. * @returns {Wick.Base} */ - - static walkItems(item) { // create paths for all the path items, this also needs to be done for the following item.className=: // 'Group', 'Layer', 'Path', 'CompoundPath', 'Shape', 'Raster', 'SymbolItem', 'PointText' @@ -55505,16 +54800,15 @@ Wick.SVGAsset = class extends Wick.FileAsset { var frame = new Wick.Frame(); wickItem.addFrame(frame); var groupChildren = Array.from(item.children); //prevent any side effects - groupChildren.forEach(childItem => { var wickChildItem = Wick.SVGAsset.walkItems(childItem).copy(); - if (wickChildItem instanceof Wick.Clip) { frame.addClip(wickChildItem); } else if (wickChildItem instanceof Wick.Path) { frame.addPath(wickChildItem); } else if (wickChildItem instanceof Wick.Layer) { - frame.addLayer(wickChildItem); //console.error("SVG Import: Error importing, nested layers.ignoring."); // Insert text + frame.addLayer(wickChildItem); + //console.error("SVG Import: Error importing, nested layers.ignoring."); // Insert text } else { console.error("SVG Import: Unknown item type.".concat(wickChildItem.classname)); // Insert text } @@ -55524,12 +54818,10 @@ Wick.SVGAsset = class extends Wick.FileAsset { var wickObjects = []; var layers = []; var groupChildren = Array.from(item.children); //prevent any side effects - groupChildren.forEach(childItem => { - var clipActiveLayer = wickItem.activeLayer; ///This should be clips and paths not layers - + var clipActiveLayer = wickItem.activeLayer; + ///This should be clips and paths not layers var walkItem = Wick.SVGAsset.walkItems(childItem).copy(); - if (walkItem instanceof Wick.Layer) { //console.error("SVG Import: Clip has a child that is a layer, this should never happen. ignoring."); // Insert text layers.push(walkItem); @@ -55540,9 +54832,7 @@ Wick.SVGAsset = class extends Wick.FileAsset { }); wickItem.addObjects(wickObjects); //add the items to the project // add layers after onjects so the objexts don't get bound to the new layer - var layersCopy = Array.from(layers); //prevent any side effects - layersCopy.forEach(layer => { wickItem.timeline.addLayer(layer); }); @@ -55556,7 +54846,6 @@ Wick.SVGAsset = class extends Wick.FileAsset { json: item.exportJSON() }); } - return wickItem; } /** @@ -55568,11 +54857,8 @@ Wick.SVGAsset = class extends Wick.FileAsset { * Walks through the items tree converting shapes into paths. This should be possible to do in the walkitems routine * @param {Paper.Item} item - called when the Path is done loading. */ - - static _breakAppartShapesRecursively(item) { item.applyMatrix = true; - if (item instanceof paper.Group || item instanceof paper.Layer) { var children = Array.from(item.children); children.forEach(childItem => { @@ -55580,19 +54866,19 @@ Wick.SVGAsset = class extends Wick.FileAsset { }); } else if (item instanceof paper.Shape) { //This should have been done automatically by the import options, spo shouldn't be needed - var path = item.toPath(); //item.parent.addChild(path); + var path = item.toPath(); + //item.parent.addChild(path); //path.insertAbove(item); //item.remove(); - item.replaceWith(path); } } + /** * Creates a new Wick SVG that uses this asset's data. * @param {function} callback - called when the SVG is done loading. */ - createInstance(callback) { // needs to take a base64 encoded string. //we need a viewSVG and an SVG object that extends base by the looks of things. @@ -55626,16 +54912,12 @@ Wick.SVGAsset = class extends Wick.FileAsset { expandShapes: true, insert: false }); - Wick.SVGAsset._breakAppartShapesRecursively(item); - var wickItem = Wick.SVGAsset.walkItems(item).copy(); callback(wickItem); }; - Wick.SVGFile.fromSVGFile(this.src, importSVG); } - }; /* * Copyright 2020 WICKLETS LLC @@ -55666,20 +54948,18 @@ Wick.Tickable = class extends Wick.Base { static get LOG_ERRORS() { return false; } + /** * Returns a list of all possible events for this object. * @return {string[]} Array of all possible scripts. */ - - static get possibleScripts() { return ['default', 'mouseenter', 'mousedown', 'mousepressed', 'mousereleased', 'mouseleave', 'mousehover', 'mousedrag', 'mouseclick', 'keypressed', 'keyreleased', 'keydown', 'load', 'update', 'unload']; } + /** * Create a new tickable object. */ - - constructor(args) { if (!args) args = {}; super(args); @@ -55694,10 +54974,8 @@ Wick.Tickable = class extends Wick.Base { this._onEventFns = {}; this._cachedScripts = {}; } - _deserialize(data) { super._deserialize(data); - this._onscreen = false; this._onscreenLastTick = false; this._mouseState = 'out'; @@ -55707,139 +54985,119 @@ Wick.Tickable = class extends Wick.Base { this._onEventFns = {}; this._cachedScripts = {}; } - _serialize(args) { var data = super._serialize(args); - data.scripts = JSON.parse(JSON.stringify(this._scripts)); data.cursor = this.cursor; return data; } - get classname() { return 'Tickable'; } + /** * The scripts on this object. * @type {object[]} */ - - get scripts() { return this._scripts; } + /** * Checks if this object has a non-empty script. * @type {boolean} */ - - get hasContentfulScripts() { var hasContentfulScripts = false; - for (var script of this.scripts) { if (this.scriptIsContentful(script.name)) { hasContentfulScripts = true; } } - return hasContentfulScripts; } + /** * Check if this object is currently visible in the project, based on its parent. * @type {boolean} */ - - get onScreen() { if (!this.parent) return false; return this.parent.onScreen; } + /** * Add a function to be called when an event happens. * @param {string} name - The name of the event to attach the function to. * @param {function} fn - The function to call when the given event happens. */ - - onEvent(name, fn) { if (Wick.Tickable.possibleScripts.indexOf(name) === -1) { console.warn("onEvent: " + name + " is not a valid event name."); return; } - this.addEventFn(name, fn); } + /** * Attach a function to a given event. * @param {string} name - the name of the event to attach a function to. * @param {function} fn - the function to attach */ - - addEventFn(name, fn) { this.getEventFns(name).push(fn); } + /** * Gets all functions attached to an event with a given name. * @param {string} - The name of the event */ - - getEventFns(name) { if (!this._onEventFns[name]) { this._onEventFns[name] = []; } - return this._onEventFns[name]; } + /** * Check if an object can have scripts attached to it. Helpful when iterating through a lot of different wick objects that may or may not be tickables. Always returns true. * @type {boolean} */ - - get isScriptable() { return true; } + /** * Add a new script to an object. * @param {string} name - The name of the event that will trigger the script. See Wick.Tickable.possibleScripts * @param {string} src - The source code of the new script. */ - - addScript(name, src) { if (Wick.Tickable.possibleScripts.indexOf(name) === -1) console.error(name + ' is not a valid script!'); - if (this.hasScript(name)) { this.updateScript(name, src); return; } - this._scripts.push({ name: name, src: '' - }); // Sort scripts by where they appear in the possibleScripts list - + }); + // Sort scripts by where they appear in the possibleScripts list var possibleScripts = Wick.Tickable.possibleScripts; - this._scripts.sort((a, b) => { return possibleScripts.indexOf(a.name) - possibleScripts.indexOf(b.name); }); - if (src) { this.updateScript(name, src); } } + /** * Get the script of this object that is triggered when the given event name happens. * @param {string} name - The name of the event. See Wick.Tickable.possibleScripts * @returns {object} the script with the given name. Can be null if the object doesn't have that script. */ - - getScript(name) { if (Wick.Tickable.possibleScripts.indexOf(name) === -1) { console.error(name + ' is not a valid script!'); @@ -55849,7 +55107,6 @@ Wick.Tickable = class extends Wick.Base { let script = this._scripts.find(script => { return script.name === name; }); - if (!script) { // Create the script if it doesn't exist. script = { @@ -55857,167 +55114,145 @@ Wick.Tickable = class extends Wick.Base { src: "" }; return script; - } // If the script is missing, add an empty. - + } + // If the script is missing, add an empty. if (!script.src) { script.src = ""; } - return script; } } + /** * Returns a list of script names which are not currently in use for this object. * @return {string[]} Available script names. */ - - getAvailableScripts() { return Wick.Tickable.possibleScripts.filter(script => !this.hasScript(script)); } + /** * Check if the object has a script with the given event name. * @param {string} name - The name of the event. See Wick.Tickable.possibleScripts * @returns {boolean} True if the script with the given name exists */ - - hasScript(name) { let script = this.scripts.find(script => script.name === name); - if (script) { return true; } - return false; } + /** * Check if the object has a non-empty script with a given name. * @param {string} name - The name of the event. See Wick.Tickable.possibleScripts * @returns {boolean} True if the script with the given name has code */ - - scriptIsContentful(name) { if (!this.hasScript(name)) { return false; } - var script = this.getScript(name); - if (script && script.src.trim() !== '') { return true; } - return false; } + /** * Changes the source of the script with the given event name. * @param {string} name - The name of the event that will trigger the script. See Wick.Tickable.possibleScripts * @param {string} src - The source code of the script. */ - - updateScript(name, src) { if (!src) src = ""; // Reset script if it is not defined. - this.getScript(name).src = src; delete this._cachedScripts[name]; } + /** * Remove the script that corresponds to a given event name. * @param {string} name - The name of the event. See Wick.Tickable.possibleScripts */ - - removeScript(name) { this._scripts = this._scripts.filter(script => { return script.name !== name; }); } + /** * Schedule a script to run at the end of the tick. * @param {string} name - The name of the script to run. See Tickable.possibleScripts * @param {Object} parameters - An object consisting of key,value pairs which correspond to parameters to pass to the script. */ - - scheduleScript(name, parameters) { if (!this.project) return; this.project.scheduleScript(this.uuid, name, parameters); } + /** * Run the script with the corresponding event name. Will not run the script if the object is marked as removed. * @param {string} name - The name of the event. See Wick.Tickable.possibleScripts * @param {Object} parameters - An object containing key,value pairs of parameters to send to the script. * @returns {object} object containing error info if an error happened. Returns null if there was no error (script ran successfully) */ - - runScript(name, parameters) { if (this.removed || !this.onScreen) { return; } - if (!Wick.Tickable.possibleScripts.indexOf(name) === -1) { console.error(name + ' is not a valid script!'); - } // Don't run scripts if this object is the focus + } + // Don't run scripts if this object is the focus // (this makes it so preview play will always play, even if the parent Clip of the timeline has a stop script) - - if (this.project && this.project.focus === this) { return null; - } // Run functions attached using onEvent - + } + // Run functions attached using onEvent var eventFnError = null; this.getEventFns(name).forEach(eventFn => { if (eventFnError) return; eventFnError = this._runFunction(eventFn, name, parameters); }); - if (eventFnError) { this.project.error = eventFnError; return; - } // Run function inside tab - + } + // Run function inside tab if (this.scriptIsContentful(name)) { var script = this.getScript(name); - var fn = this._cachedScripts[name] || this._evalScript(name, script.src); - if (!(fn instanceof Function)) { return fn; // error } this._cachedScripts[name] = fn; - var error = this._runFunction(fn, name, parameters); - if (error && this.project) { this.project.error = error; return; } } } + /** * The tick routine to be called when the object ticks. * @returns {object} - An object with information about the result from ticking. Null if no errors occured, and the script ran successfully. */ - - tick() { // Update named child references - this._attachChildClipReferences(); // Update onScreen flags. - + this._attachChildClipReferences(); + // Update onScreen flags. this._onscreenLastTick = this._onscreen; - this._onscreen = this.onScreen; // Update mouse states. + this._onscreen = this.onScreen; + // Update mouse states. this._lastMouseState = this._mouseState; - if (this.project && this.project.objectIsMouseTarget(this)) { if (this.project.isMouseDown) { this._mouseState = 'down'; @@ -56026,9 +55261,9 @@ Wick.Tickable = class extends Wick.Base { } } else { this._mouseState = 'out'; - } // Call tick event function that corresponds to state. - + } + // Call tick event function that corresponds to state. if (!this._onscreen && !this._onscreenLastTick) { this._onInactive(); } else if (this._onscreen && !this._onscreenLastTick) { @@ -56039,77 +55274,77 @@ Wick.Tickable = class extends Wick.Base { this._onDeactivated(); } } - - _onInactive() {// Do nothing. + _onInactive() { + // Do nothing. } - _onActivated() { this.runScript('default'); // Run the script immediately. - this.scheduleScript('load'); } - _onActive() { this.scheduleScript('update'); var current = this._mouseState; - var last = this._lastMouseState; // Mouse enter + var last = this._lastMouseState; + // Mouse enter if (last === 'out' && current !== 'out') { this.scheduleScript('mouseenter'); - } // Mouse down - + } + // Mouse down if (current === 'down') { this.scheduleScript('mousedown'); - } // Mouse pressed - + } + // Mouse pressed if (last === 'over' && current === 'down') { this._isClickTarget = true; this.scheduleScript('mousepressed'); - } // Mouse click - + } + // Mouse click if (last === 'down' && current === 'over' && this._isClickTarget) { this.scheduleScript('mouseclick'); - } // Mouse released - + } + // Mouse released if (last === 'down' && current === 'over') { this._isClickTarget = false; this.scheduleScript('mousereleased'); - } // Mouse leave - + } + // Mouse leave if (last !== 'out' && current === 'out') { this.scheduleScript('mouseleave'); - } // Mouse hover - + } + // Mouse hover if (current === 'over') { this.scheduleScript('mousehover'); - } // Mouse drag - + } + // Mouse drag if (last === 'down' && current === 'down') { this.scheduleScript('mousedrag'); - } // Key down - + } + // Key down this.project.keysDown.forEach(key => { this.project.currentKey = key; this.scheduleScript('keydown', { key: key }); - }); // Key press + }); + // Key press this.project.keysJustPressed.forEach(key => { this.project.currentKey = key; this.scheduleScript('keypressed', { key: key }); - }); // Key released + }); + // Key released this.project.keysJustReleased.forEach(key => { this.project.currentKey = key; this.scheduleScript('keyreleased', { @@ -56117,23 +55352,22 @@ Wick.Tickable = class extends Wick.Base { }); }); } - _onDeactivated() { this._isClickTarget = false; this.scheduleScript('unload'); } - _evalScript(name, src) { - var fn = null; // Check for syntax/parsing errors + var fn = null; + // Check for syntax/parsing errors try { esprima.parseScript(src); } catch (e) { this.project.error = this._generateEsprimaErrorInfo(e, name); return; - } // Attempt to create valid function... - + } + // Attempt to create valid function... try { fn = new Function([], src); } catch (e) { @@ -56142,20 +55376,19 @@ Wick.Tickable = class extends Wick.Base { this.project.error = this._generateErrorInfo(e, name); return; } - return fn; } + /** * _runFunction runs an event function while passing in necessary global and local parameters. * @param {string} fn - Function to run. * @param {string} name - Name of the event function being run (i.e. keyDown) * @param {Object} parameters - An object of key,value pairs to be passed as parameters to the function. */ - - _runFunction(fn, name, parameters) { - var error = null; // Attach API methods + var error = null; + // Attach API methods var globalAPI = new GlobalAPI(this); var otherObjects = this.parentClip ? this.parentClip.activeNamedChildren : []; var apiMembers = globalAPI.apiMembers.concat(otherObjects.map(otherObject => { @@ -56163,8 +55396,9 @@ Wick.Tickable = class extends Wick.Base { name: otherObject.identifier, fn: otherObject }; - })); // Add in parameters, if necessary. + })); + // Add in parameters, if necessary. if (parameters) { Object.keys(parameters).forEach(parameter => { apiMembers.push({ @@ -56173,50 +55407,50 @@ Wick.Tickable = class extends Wick.Base { }); }); } - apiMembers.forEach(apiMember => { window[apiMember.name] = apiMember.fn; - }); // These are currently hacked in here for performance reasons... + }); + // These are currently hacked in here for performance reasons... var project = this.project; var root = project && project.root; window.project = root; - if (project) { window.project.resolution = { x: project.width, y: project.height }; window.project.framerate = project.framerate; - window.project.backgroundColor = project.backgroundColor; //window.project.hitTestOptions = project.hitTestOptions; + window.project.backgroundColor = project.backgroundColor; + //window.project.hitTestOptions = project.hitTestOptions; } window.root = root; window.parent = this.parentClip; - window.parentObject = this.parentObject; // Run the function + window.parentObject = this.parentObject; + // Run the function var thisScope = this instanceof Wick.Frame ? this.parentClip : this; - try { fn.bind(thisScope)(); } catch (e) { // Catch runtime errors console.error(e); error = this._generateErrorInfo(e, name); - } // These are currently hacked in here for performance reasons... - + } + // These are currently hacked in here for performance reasons... delete window.project; delete window.root; delete window.parent; - delete window.parentObject; // Detatch API methods + delete window.parentObject; + // Detatch API methods apiMembers.forEach(apiMember => { delete window[apiMember.name]; }); return error; } - _generateErrorInfo(error, name) { if (Wick.Tickable.LOG_ERRORS) console.log(error); return { @@ -56226,7 +55460,6 @@ Wick.Tickable = class extends Wick.Base { uuid: this.isClone ? this.sourceClipUUID : this.uuid }; } - _generateEsprimaErrorInfo(error, name) { if (Wick.Tickable.LOG_ERRORS) console.log(error); return { @@ -56236,7 +55469,6 @@ Wick.Tickable = class extends Wick.Base { uuid: this.uuid }; } - _generateLineNumberFromStackTrace(trace) { var lineNumber = null; trace.split('\n').forEach(line => { @@ -56244,7 +55476,6 @@ Wick.Tickable = class extends Wick.Base { var split = line.split(':'); var lineString = split[split.length - 2]; var lineInt = parseInt(lineString); - if (!isNaN(lineInt)) { lineNumber = lineInt - 2; lineNumber = lineInt; @@ -56253,10 +55484,9 @@ Wick.Tickable = class extends Wick.Base { }); return lineNumber; } - - _attachChildClipReferences() {// Implemented by Wick.Clip and Wick.Frame. + _attachChildClipReferences() { + // Implemented by Wick.Clip and Wick.Frame. } - }; /* * Copyright 2020 WICKLETS LLC @@ -56298,10 +55528,8 @@ Wick.Frame = class extends Wick.Tickable { this._soundStart = 0; this._originalLayerIndex = -1; } - _serialize(args) { var data = super._serialize(args); - data.start = this.start; data.end = this.end; data.sound = this._soundAssetUUID; @@ -56311,10 +55539,8 @@ Wick.Frame = class extends Wick.Tickable { data.originalLayerIndex = this.layerIndex !== -1 ? this.layerIndex : this._originalLayerIndex; return data; } - _deserialize(data) { super._deserialize(data); - this.start = data.start; this.end = data.end; this._soundAssetUUID = data.sound; @@ -56323,129 +55549,112 @@ Wick.Frame = class extends Wick.Tickable { this._soundStart = data.soundStart === undefined ? 0 : data.soundStart; this._originalLayerIndex = data.originalLayerIndex; } - get classname() { return 'Frame'; } + /** * The length of the frame. * @type {number} */ - - get length() { return this.end - this.start + 1; } - set length(length) { length = Math.max(1, length); var diff = length - this.length; this.end += diff; } + /** * The midpoint of the frame. * @type {number} */ - - get midpoint() { return this.start + (this.end - this.start) / 2; } + /** * Is true if the frame is currently visible. * @type {boolean} */ - - get onScreen() { if (!this.parent) return true; return this.inPosition(this.parentTimeline.playheadPosition) && this.parentClip.onScreen; } + /** * The sound attached to the frame. * @type {Wick.SoundAsset} */ - - get sound() { var uuid = this._soundAssetUUID; return uuid ? this.project.getAssetByUUID(uuid) : null; } - set sound(soundAsset) { if (!soundAsset) { this.removeSound(); return; } - this._soundAssetUUID = soundAsset.uuid; } + /** * The volume of the sound attached to the frame. * @type {number} */ - - get soundVolume() { return this._soundVolume; } - set soundVolume(soundVolume) { this._soundVolume = soundVolume; } + /** * Whether or not the sound loops. * @type {boolean} */ - - get soundLoop() { return this._soundLoop; } - set soundLoop(soundLoop) { this._soundLoop = soundLoop; } + /** * True if this frame should currently be onion skinned. */ - - get onionSkinned() { if (!this.project || !this.project.onionSkinEnabled) { return false; - } // Don't onion skin if we're in the playhead's position. - + } + // Don't onion skin if we're in the playhead's position. var playheadPosition = this.project.focus.timeline.playheadPosition; - if (this.inPosition(playheadPosition)) { return false; - } // Determine if we're in onion skinning range. - + } + // Determine if we're in onion skinning range. var onionSkinSeekBackwards = this.project.onionSkinSeekBackwards; var onionSkinSeekForwards = this.project.onionSkinSeekForwards; return this.inRange(playheadPosition - onionSkinSeekBackwards, playheadPosition + onionSkinSeekForwards); } + /** * Removes the sound attached to this frame. */ - - removeSound() { this._soundAssetUUID = null; } + /** * Plays the sound attached to this frame. */ - - playSound() { if (!this.sound) { return; } - var options = { seekMS: this.playheadSoundOffsetMS + this.soundStart, volume: this.soundVolume, @@ -56454,75 +55663,67 @@ Wick.Frame = class extends Wick.Tickable { }; this._soundID = this.project.playSoundFromAsset(this.sound, options); } + /** * Stops the sound attached to this frame. */ - - stopSound() { if (this.sound) { this.sound.stop(this._soundID); this._soundID = null; } } + /** * Check if the sound on this frame is playing. * @returns {boolean} true if the sound is playing */ - - isSoundPlaying() { return this._soundID !== null; } + /** * The amount of time, in milliseconds, that the frame's sound should play before stopping. * @type {number} */ - - get playheadSoundOffsetMS() { var offsetFrames = this.parentTimeline.playheadPosition - this.start; var offsetMS = 1000 / this.project.framerate * offsetFrames; return offsetMS; } + /** * The amount of time the sound playing should be offset, in milliseconds. If this is 0, * the sound plays normally. A negative value means the sound should start at a later point * in the track. THIS DOES NOT DETERMINE WHEN A SOUND PLAYS. * @type {number} */ - - get soundStart() { return this._soundStart; } - set soundStart(val) { this._soundStart = val; } + /** * When should the sound start, in milliseconds. * @type {number} */ - - get soundStartMS() { return 1000 / this.project.framerate * (this.start - 1); } + /** * When should the sound end, in milliseconds. * @type {number} */ - - get soundEndMS() { return 1000 / this.project.framerate * this.end; } + /** * Returns the frame's start position in relation to the root timeline. */ - - get projectFrameStart() { if (this.parentClip.isRoot) { return this.start; @@ -56531,50 +55732,45 @@ Wick.Frame = class extends Wick.Tickable { return val; } } + /** * The paths on the frame. * @type {Wick.Path[]} */ - - get paths() { return this.getChildren('Path'); } + /** * The paths that are text and have identifiers, for dynamic text. * @type {Wick.Path[]} */ - - get dynamicTextPaths() { return this.paths.filter(path => { return path.isDynamicText; }); } + /** * The clips on the frame. * @type {Wick.Clip[]} */ - - get clips() { return this.getChildren(['Clip', 'Button']); } + /** * The drawable objectson the frame. * @type {Wick.Base[]} */ - - get drawable() { return this.getChildren(['Clip', 'Button', 'Path']); } + /** * The tweens on this frame. * @type {Wick.Tween[]} */ - - get tweens() { // Ensure no tweens are outside of this frame's length. var tweens = this.getChildren('Tween'); @@ -56583,182 +55779,161 @@ Wick.Frame = class extends Wick.Tickable { }); return this.getChildren('Tween'); } + /** * True if there are clips or paths on the frame. * @type {boolean} */ - - get contentful() { return this.paths.filter(path => { return !path.view.item.data._isPlaceholder; }).length > 0 || this.clips.length > 0; } + /** * The index of the parent layer. * @type {number} */ - - get layerIndex() { return this.parentLayer ? this.parentLayer.index : -1; } + /** * The index of the layer that this frame last belonged to. Used when copying and pasting frames. * @type {number} */ - - get originalLayerIndex() { return this._originalLayerIndex; } + /** * Removes this frame from its parent layer. */ - - remove() { this.parent.removeFrame(this); } + /** * True if the playhead is on this frame. * @param {number} playheadPosition - the position of the playhead. * @return {boolean} */ - - inPosition(playheadPosition) { return this.start <= playheadPosition && this.end >= playheadPosition; } + /** * True if the frame exists within the given range. * @param {number} start - the start of the range to check. * @param {number} end - the end of the range to check. * @return {boolean} */ - - inRange(start, end) { return this.inPosition(start) || this.inPosition(end) || this.start >= start && this.start <= end || this.end >= start && this.end <= end; } + /** * True if the frame is contained fully within a given range. * @param {number} start - the start of the range to check. * @param {number} end - the end of the range to check. * @return {boolean} */ - - containedWithin(start, end) { return this.start >= start && this.end <= end; } + /** * The number of frames that this frame is from a given playhead position. * @param {number} playheadPosition */ - - distanceFrom(playheadPosition) { // playhead position is inside frame, distance is zero. if (this.start <= playheadPosition && this.end >= playheadPosition) { return 0; - } // otherwise, find the distance from the nearest end - + } + // otherwise, find the distance from the nearest end if (this.start >= playheadPosition) { return this.start - playheadPosition; } else if (this.end <= playheadPosition) { return playheadPosition - this.end; } } + /** * Add a clip to the frame. * @param {Wick.Clip} clip - the clip to add. */ - - addClip(clip) { if (clip.parent) { clip.remove(); } + this.addChild(clip); - this.addChild(clip); // Pre-render the clip's frames + // Pre-render the clip's frames // (this fixes an issue where clips created from ClipAssets would be "missing" frames) - clip.timeline.getAllFrames(true).forEach(frame => { frame.view.render(); }); } + /** * Remove a clip from the frame. * @param {Wick.Clip} clip - the clip to remove. */ - - removeClip(clip) { this.removeChild(clip); } + /** * Add a path to the frame. * @param {Wick.Path} path - the path to add. */ - - addPath(path) { if (path.parent) { path.remove(); } - this.addChild(path); } + /** * Remove a path from the frame. * @param {Wick.Path} path - the path to remove. */ - - removePath(path) { this.removeChild(path); } + /** * Add a tween to the frame. * @param {Wick.Tween} tween - the tween to add. */ - - addTween(tween) { // New tweens eat existing tweens. var otherTween = this.getTweenAtPosition(tween.playheadPosition); - if (otherTween) { otherTween.remove(); } - this.addChild(tween); tween.restrictToFrameSize(); } + /** * Automatically creates a tween at the current playhead position. Converts all objects into one clip if needed. */ - - createTween() { // Don't make a tween if one already exits var playheadPosition = this.getRelativePlayheadPosition(); - if (this.getTweenAtPosition(playheadPosition)) { return; - } // If more than one object exists on the frame, or if there is only one path, create a clip from those objects - + } + // If more than one object exists on the frame, or if there is only one path, create a clip from those objects var clips = this.clips; var paths = this.paths; - if (clips.length === 0 && paths.length === 1 || clips.length + paths.length > 1) { var allDrawables = paths.concat(clips); - var center = this.project.selection.view._getObjectsBounds(allDrawables).center; - var clip = new Wick.Clip({ transformation: new Wick.Transformation({ x: center.x, @@ -56767,74 +55942,66 @@ Wick.Frame = class extends Wick.Tickable { }); this.addClip(clip); clip.addObjects(allDrawables); - } // Create the tween (if there's not already a tween at the current playhead position) - + } + // Create the tween (if there's not already a tween at the current playhead position) var clip = this.clips[0]; this.addTween(new Wick.Tween({ playheadPosition: playheadPosition, transformation: clip ? clip.transformation.copy() : new Wick.Transformation() })); } + /** * Remove a tween from the frame. * @param {Wick.Tween} tween - the tween to remove. */ - - removeTween(tween) { this.removeChild(tween); } + /** * Remove all tweens from this frame. */ - - removeAllTweens(tween) { this.tweens.forEach(tween => { tween.remove(); }); } + /** * Get the tween at the given playhead position. Returns null if there is no tween. * @param {number} playheadPosition - the playhead position to look for tweens at. * @returns {Wick.Tween || null} the tween at the given playhead position. */ - - getTweenAtPosition(playheadPosition) { return this.tweens.find(tween => { return tween.playheadPosition === playheadPosition; }) || null; } + /** * Returns the tween at the current playhead position, if one exists on the frame. Null otherwise. * @returns {Wick.Tween || null} */ - - getTweenAtCurrentPlayheadPosition() { let playheadPosition = this.getRelativePlayheadPosition(); return this.getTweenAtPosition(playheadPosition); } + /** * The tween being used to transform the objects on the frame. * @returns {Wick.Tween || null} tween - the active tween. Null if there is no active tween. */ - - getActiveTween() { if (!this.parentTimeline) return null; var playheadPosition = this.getRelativePlayheadPosition(); var tween = this.getTweenAtPosition(playheadPosition); - if (tween) { return tween; } - var seekBackwardsTween = this.seekTweenBehind(playheadPosition); var seekForwardsTween = this.seekTweenInFront(playheadPosition); - if (seekBackwardsTween && seekForwardsTween) { return Wick.Tween.interpolate(seekBackwardsTween, seekForwardsTween, playheadPosition); } else if (seekForwardsTween) { @@ -56845,84 +56012,80 @@ Wick.Frame = class extends Wick.Tickable { return null; } } + /** * Applies the transformation of current tween to the objects on the frame. */ - - applyTweenTransforms() { var tween = this.getActiveTween(); - if (tween) { this.clips.forEach(clip => { tween.applyTransformsToClip(clip); }); } } + /** * Applies single frame positions to timelines if necessary. */ - - applyClipSingleFramePositions() { this.clips.forEach(clip => { clip.applySingleFramePosition(); }); } + /** * Update all clip timelines for their animation type. */ - - updateClipTimelinesForAnimationType() { this.clips.forEach(clip => { clip.updateTimelineForAnimationType(); }); } + /** * The asset of the sound attached to this frame, if one exists * @returns {Wick.Asset[]} */ - - getLinkedAssets() { var linkedAssets = []; - if (this.sound) { linkedAssets.push(this.sound); } - return linkedAssets; } + /** * Cut this frame in half using the parent timeline's playhead position. */ - - cut() { // Can't cut a frame that doesn't beolong to a timeline + layer - if (!this.parentTimeline) return; // Can't cut a frame with length 1 + if (!this.parentTimeline) return; - if (this.length === 1) return; // Can't cut a frame that isn't under the playhead + // Can't cut a frame with length 1 + if (this.length === 1) return; + // Can't cut a frame that isn't under the playhead var playheadPosition = this.parentTimeline.playheadPosition; - if (!this.inPosition(playheadPosition)) return; // Create right half (leftover) frame + if (!this.inPosition(playheadPosition)) return; + // Create right half (leftover) frame var rightHalf = this.copy(); rightHalf.identifier = null; rightHalf.removeSound(); rightHalf.removeAllTweens(); - rightHalf.start = playheadPosition = playheadPosition; // Cut this frame shorter + rightHalf.start = playheadPosition = playheadPosition; - this.end = playheadPosition - 1; // Add right frame + // Cut this frame shorter + this.end = playheadPosition - 1; + // Add right frame this.parentLayer.addFrame(rightHalf); } + /** * Extend this frame by one and push all frames right of this frame to the right. */ - - extendAndPushOtherFrames() { this.parentLayer.getFramesInRange(this.end + 1, Infinity).forEach(frame => { frame.start += 1; @@ -56930,11 +56093,10 @@ Wick.Frame = class extends Wick.Tickable { }); this.end += 1; } + /** * Shrink this frame by one and pull all frames left of this frame to the left. */ - - shrinkAndPullOtherFrames() { if (this.length === 1) return; this.parentLayer.getFramesInRange(this.end + 1, Infinity).forEach(frame => { @@ -56943,107 +56105,84 @@ Wick.Frame = class extends Wick.Tickable { }); this.end -= 1; } + /** * Import SVG data into this frame. SVGs containing mulitple paths will be split into multiple Wick Paths. * @param {string} svg - the SVG data to parse and import. */ - /* importSVG (svg) { this.view.importSVG(svg); } */ - /** * Get the position of this frame in relation to the parent timeline's playhead position. * @returns {number} */ - - getRelativePlayheadPosition() { return this.parentTimeline.playheadPosition - this.start + 1; } + /** * Find the first tween on this frame that exists behind the given playhead position. * @returns {Wick.Tween} */ - - seekTweenBehind(playheadPosition) { var seekBackwardsPosition = playheadPosition; var seekBackwardsTween = null; - while (seekBackwardsPosition > 0) { seekBackwardsTween = this.getTweenAtPosition(seekBackwardsPosition); seekBackwardsPosition--; if (seekBackwardsTween) break; } - return seekBackwardsTween; } + /** * Find the first tween on this frame that exists past the given playhead position. * @returns {Wick.Tween} */ - - seekTweenInFront(playheadPosition) { var seekForwardsPosition = playheadPosition; var seekForwardsTween = null; - while (seekForwardsPosition <= this.end) { seekForwardsTween = this.getTweenAtPosition(seekForwardsPosition); seekForwardsPosition++; if (seekForwardsTween) break; } - return seekForwardsTween; } - _onInactive() { super._onInactive(); - this._tickChildren(); } - _onActivated() { super._onActivated(); - this.playSound(); - this._tickChildren(); } - _onActive() { super._onActive(); - this._tickChildren(); } - _onDeactivated() { super._onDeactivated(); - this.stopSound(); - this._tickChildren(); } - _tickChildren() { this.clips.forEach(clip => { clip.tick(); }); } - _attachChildClipReferences() { this.clips.forEach(clip => { if (clip.identifier) { this[clip.identifier] = clip; - clip._attachChildClipReferences(); } }); } - }; /* * Copyright 2020 WICKLETS LLC @@ -57079,14 +56218,13 @@ Wick.Clip = class extends Wick.Tickable { 'playOnce': 'Play Once' }; } + /** * Create a new clip. * @param {string} identifier - The identifier of the new clip. * @param {Wick.Path|Wick.Clip[]} objects - Optional. A list of objects to add to the clip. * @param {Wick.Transformation} transformation - Optional. The initial transformation of the clip. */ - - constructor(args) { if (!args) args = {}; super(args); @@ -57094,9 +56232,7 @@ Wick.Clip = class extends Wick.Tickable { this.timeline.addLayer(new Wick.Layer()); this.timeline.activeLayer.addFrame(new Wick.Frame()); this._animationType = 'loop'; // Can be one of loop, oneFrame, single - this._singleFrameNumber = 1; // Default to 1, this value is only used if the animation type is single - this._playedOnce = false; this._isSynced = false; this._transformation = args.transformation || new Wick.Transformation(); @@ -57104,18 +56240,15 @@ Wick.Clip = class extends Wick.Tickable { this._isClone = false; this._sourceClipUUID = null; this._assetSourceUUID = null; - /* If objects are passed in, add them to the clip and reposition them */ + /* If objects are passed in, add them to the clip and reposition them */ if (args.objects) { this.addObjects(args.objects); } - this._clones = []; } - _serialize(args) { var data = super._serialize(args); - data.transformation = this.transformation.values; data.timeline = this._timeline; data.animationType = this._animationType; @@ -57124,10 +56257,8 @@ Wick.Clip = class extends Wick.Tickable { data.isSynced = this._isSynced; return data; } - _deserialize(data) { super._deserialize(data); - this.transformation = new Wick.Transformation(data.transformation); this._timeline = data.timeline; this._animationType = data.animationType || 'loop'; @@ -57137,16 +56268,14 @@ Wick.Clip = class extends Wick.Tickable { this._playedOnce = false; this._clones = []; } - get classname() { return 'Clip'; } + /** * Determines whether or not the clip is visible in the project. * @type {boolean} */ - - get onScreen() { if (this.isRoot) { return true; @@ -57154,116 +56283,100 @@ Wick.Clip = class extends Wick.Tickable { return this.parentFrame.onScreen; } } + /** * Determines whether or not the clip is the root clip in the project. * @type {boolean} */ - - get isRoot() { return this.project && this === this.project.root; } + /** * True if the clip should sync to the timeline's position. * @type {boolean} */ - - get isSynced() { let isSingle = this.animationType === 'single'; return this._isSynced && !isSingle && !this.isRoot; } - set isSynced(bool) { if (!(typeof bool === 'boolean')) { return; } - this._isSynced = bool; - if (bool) { this.applySyncPosition(); } else { this.timeline.playheadPosition = 1; } } + /** * Determines whether or not the clip is the currently focused clip in the project. * @type {boolean} */ - - get isFocus() { return this.project && this === this.project.focus; } + /** * Check if a Clip is a clone of another object. * @type {boolean} */ - - get isClone() { return this._isClone; } + /** * The uuid of the clip that this clip was cloned from. * @type {string} */ - - get sourceClipUUID() { return this._sourceClipUUID; } + /** * Returns the source clip of this clip if this clip is a clone. Null otherwise. * */ - - get sourceClip() { if (!this.sourceClipUUID) return null; return this.project.getObjectByUUID(this.sourceClipUUID); } + /** * The uuid of the ClipAsset that this clip was created from. * @type {string} */ - - get assetSourceUUID() { return this._assetSourceUUID; } - set assetSourceUUID(assetSourceUUID) { this._assetSourceUUID = assetSourceUUID; } + /** * The timeline of the clip. * @type {Wick.Timeline} */ - - get timeline() { return this.getChild('Timeline'); } - set timeline(timeline) { if (this.timeline) { this.removeChild(this.timeline); } - this.addChild(timeline); } + /** * The animation type of the clip. Must be of a type represented within animationTypes; * @type {string} */ - - get animationType() { return this._animationType; } - set animationType(animationType) { // Default to loop if an invalid animation type is passed in. if (!Wick.Clip.animationTypes[animationType]) { @@ -57274,12 +56387,11 @@ Wick.Clip = class extends Wick.Tickable { this.resetTimelinePosition(); } } + /** * The frame to display when animation type is set to singleFrame. * @type {number} */ - - get singleFrameNumber() { if (this.animationType !== 'single') { return null; @@ -57287,7 +56399,6 @@ Wick.Clip = class extends Wick.Tickable { return this._singleFrameNumber; } } - set singleFrameNumber(frame) { // Constrain to be within the length of the clip. if (frame < 1) { @@ -57295,101 +56406,95 @@ Wick.Clip = class extends Wick.Tickable { } else if (frame > this.timeline.length) { frame = this.timeline.length; } - this._singleFrameNumber = frame; this.applySingleFramePosition(); } + /** * The frame to display when the clip is synced * @type {number} */ - - get syncFrame() { - let timelineOffset = this.parentClip.timeline.playheadPosition - this.parentFrame.start; // Show the last frame if we're past it on a playOnce Clip. + let timelineOffset = this.parentClip.timeline.playheadPosition - this.parentFrame.start; + // Show the last frame if we're past it on a playOnce Clip. if (this.animationType === 'playOnce' && timelineOffset >= this.timeline.length) { return this.timeline.length; - } // Otherwise, show the correct frame. - + } + // Otherwise, show the correct frame. return timelineOffset % this.timeline.length + 1; } + /** * Returns true if the clip has been played through fully once. * @type {boolean} */ - - get playedOnce() { return this._playedOnce; } - set playedOnce(bool) { return this._playedOnce = bool; } + /** * The active layer of the clip's timeline. * @type {Wick.Layer} */ - - get activeLayer() { return this.timeline.activeLayer; } + /** * The active frame of the clip's timeline. * @type {Wick.Frame} */ - - get activeFrame() { return this.activeLayer.activeFrame; } + /** * An array containing every clip and frame that is a child of this clip and has an identifier. * @type {Wick.Base[]} */ - - get namedChildren() { var namedChildren = []; this.timeline.frames.forEach(frame => { // Objects that can be accessed by their identifiers: + // Frames if (frame.identifier) { namedChildren.push(frame); - } // Clips - + } + // Clips frame.clips.forEach(clip => { if (clip.identifier) { namedChildren.push(clip); } - }); // Dynamic text paths + }); + // Dynamic text paths frame.dynamicTextPaths.forEach(path => { namedChildren.push(path); }); }); return namedChildren; } + /** * An array containing every clip and frame that is a child of this clip and has an identifier, and also is visible on screen. * @type {Wick.Base[]} */ - - get activeNamedChildren() { return this.namedChildren.filter(child => { return child.onScreen; }); } + /** * Resets the clip's timeline position. */ - - resetTimelinePosition() { if (this.animationType === 'single') { this.applySingleFramePosition(); @@ -57397,74 +56502,69 @@ Wick.Clip = class extends Wick.Tickable { this.timeline.playheadPosition = 1; // Reset timeline position if we are not on single frame. } } + /** * Updates the frame's single frame positions if necessary. Only works if the clip's animationType is 'single'. */ - - applySingleFramePosition() { if (this.animationType === 'single') { // Ensure that the single frame we've chosen is reflected no matter what. this.timeline.playheadPosition = this.singleFrameNumber; } } + /** * Updates the clip's playhead position if the Clip is in sync mode */ - - applySyncPosition() { if (this.isSynced) { this.timeline.playheadPosition = this.syncFrame; } } + /** * Updates the timeline of the clip based on the animation type of the clip. */ - - updateTimelineForAnimationType() { if (this.animationType === 'single') { this.applySingleFramePosition(); } - if (this.isSynced) { this.applySyncPosition(); } } + /** * Remove a clone from the clones array by uuid. * @param {string} uuid */ - - removeClone(uuid) { if (this.isClone) return; this._clones = this.clones.filter(obj => obj.uuid !== uuid); } + /** * Remove this clip from its parent frame. */ - - remove() { // Don't attempt to remove if the object has already been removed. // (This is caused by calling remove() multiple times on one object inside a script.) if (!this.parent || this._willBeRemoved) return; - this._willBeRemoved = true; // Force unload to run now, before object is removed; + this._willBeRemoved = true; - this.runScript('unload'); // Remove from the clones array. + // Force unload to run now, before object is removed; + this.runScript('unload'); + // Remove from the clones array. this.sourceClip && this.sourceClip.removeClone(this.uuid); this.parent.removeClip(this); this.removed = true; } + /** * Remove this clip and add all of its paths and clips to its parent frame. * @returns {Wick.Base[]} the objects that were inside the clip. */ - - breakApart() { var leftovers = []; this.timeline.activeFrames.forEach(frame => { @@ -57484,12 +56584,11 @@ Wick.Clip = class extends Wick.Tickable { this.remove(); return leftovers; } + /** * Add paths and clips to this clip. * @param {Wick.Base[]} objects - the paths and clips to add to the clip */ - - addObjects(objects) { // Reposition objects such that their origin point is equal to this Clip's position objects.forEach(object => { @@ -57504,119 +56603,107 @@ Wick.Clip = class extends Wick.Tickable { } }); } + /** * Stops a clip's timeline on that clip's current playhead position. */ - - stop() { this.timeline.stop(); } + /** * Plays a clip's timeline from that clip's current playhead position. */ - - play() { this.timeline.play(); } + /** * Moves a clip's playhead to a specific position and stops that clip's timeline on that position. * @param {number|string} frame - number or string representing the frame to move the playhead to. */ - - gotoAndStop(frame) { this.timeline.gotoAndStop(frame); this.applySingleFramePosition(); } + /** * Moves a clip's playhead to a specific position and plays that clip's timeline from that position. * @param {number|string} frame - number or string representing the frame to move the playhead to. */ - - gotoAndPlay(frame) { this.timeline.gotoAndPlay(frame); this.applySingleFramePosition(); } + /** * Move the playhead of the clips timeline forward one frame. Does nothing if the clip is on its last frame. */ - - gotoNextFrame() { this.timeline.gotoNextFrame(); this.applySingleFramePosition(); } + /** * Move the playhead of the clips timeline backwards one frame. Does nothing if the clip is on its first frame. */ - - gotoPrevFrame() { this.timeline.gotoPrevFrame(); this.applySingleFramePosition(); } + /** * Returns the name of the frame which is currently active. If multiple frames are active, returns the name of the first active frame. * @returns {string} Active Frame name. If the active frame does not have an identifier, returns empty string. */ - - get currentFrameName() { let frames = this.timeline.activeFrames; let name = ''; frames.forEach(frame => { if (name) return; - if (frame.identifier) { name = frame.identifier; } }); return name; } + /** * @deprecated * Returns the current playhead position. This is a legacy function, you should use clip.playheadPosition instead. * @returns {number} Playhead Position. */ - - get currentFrameNumber() { return this.timeline.playheadPosition; } + /** * The current transformation of the clip. * @type {Wick.Transformation} */ - - get transformation() { return this._transformation; } - set transformation(transformation) { - this._transformation = transformation; // When the transformation changes, update the current tween, if one exists + this._transformation = transformation; + // When the transformation changes, update the current tween, if one exists if (this.parentFrame) { // This tween must only ever be the tween over the current playhead position. // Altering the active tween will overwrite tweens when moving between frames. var tween = this.parentFrame.getTweenAtCurrentPlayheadPosition(); - if (tween) { tween.transformation = this._transformation.copy(); } } } + /** * Perform circular hit test with other clip. * @param {Wick.Clip} other - the clip to hit test with * @param {object} options - Hit test options * @returns {object} Hit information */ - - circleHits(other, options) { let bounds1 = this.absoluteBounds; let bounds2 = other.absoluteBounds; @@ -57625,28 +56712,25 @@ Wick.Clip = class extends Wick.Tickable { let distance = Math.sqrt((c1.x - c2.x) * (c1.x - c2.x) + (c1.y - c2.y) * (c1.y - c2.y)); let r1 = options.radius ? options.radius : this.radius; let r2 = other.radius; //should add option for other radius? - - let overlap = r1 + r2 - distance; // TODO: Maybe add a case for overlap === 0? - + let overlap = r1 + r2 - distance; + // TODO: Maybe add a case for overlap === 0? if (overlap > 0) { let x = c1.x - c2.x; let y = c1.y - c2.y; let magnitude = Math.sqrt(x * x + y * y); x = x / magnitude; - y = y / magnitude; // is now a normalized vector from c2 to c1 + y = y / magnitude; + // is now a normalized vector from c2 to c1 let result = {}; - if (options.overlap) { result.overlapX = overlap * x; result.overlapY = overlap * y; } - if (options.offset) { result.offsetX = overlap * x; result.offsetY = overlap * y; } - if (options.intersections) { if (r2 - distance > r1 || r1 - distance > r2 || distance === 0) { result.intersections = []; @@ -57665,27 +56749,24 @@ Wick.Clip = class extends Wick.Tickable { }]; } } - return result; } - return null; } + /** * Perform rectangular hit test with other clip. * @param {Wick.Clip} other - the clip to hit test with * @param {object} options - Hit test options * @returns {object} Hit information */ - - rectangleHits(other, options) { let bounds1 = this.absoluteBounds; - let bounds2 = other.absoluteBounds; // TODO: write intersects so we don't rely on paper Rectangle objects + let bounds2 = other.absoluteBounds; + // TODO: write intersects so we don't rely on paper Rectangle objects if (bounds1.intersects(bounds2)) { let result = {}; - if (options.overlap) { // Find the direction along which we have to travel the least distance to no longer overlap let left = bounds2.left - bounds1.right; @@ -57694,28 +56775,25 @@ Wick.Clip = class extends Wick.Tickable { let down = bounds2.bottom - bounds1.top; let overlapX = Math.abs(left) < Math.abs(right) ? left : right; let overlapY = Math.abs(up) < Math.abs(down) ? up : down; - if (Math.abs(overlapX) < Math.abs(overlapY)) { overlapY = 0; } else { overlapX = 0; } - result.overlapX = overlapX; result.overlapY = overlapY; } - if (options.offset) { // Find how far along the center to center vector we must travel to no longer overlap let vectorX = bounds1.center.x - bounds2.center.x; let vectorY = bounds1.center.y - bounds2.center.y; let magnitude = Math.sqrt(vectorX * vectorX + vectorY * vectorY); vectorX /= magnitude; - vectorY /= magnitude; // Choose p1, p2, based on quadrant of center to center vector + vectorY /= magnitude; + // Choose p1, p2, based on quadrant of center to center vector let p1 = vectorX > 0 ? vectorY > 0 ? bounds1.topLeft : bounds1.bottomLeft : vectorY > 0 ? bounds1.topRight : bounds1.bottomRight; let p2 = vectorX > 0 ? vectorY > 0 ? bounds2.bottomRight : bounds2.topRight : vectorY > 0 ? bounds2.bottomLeft : bounds2.topLeft; - if (Math.abs(p2.x - p1.x) < Math.abs((p2.y - p1.y) * vectorX / vectorY)) { result.offsetX = p2.x - p1.x; result.offsetY = result.offsetX * vectorY / vectorX; @@ -57724,27 +56802,25 @@ Wick.Clip = class extends Wick.Tickable { result.offsetX = result.offsetY * vectorX / vectorY; } } - if (options.intersections) { result.intersections = []; let ps1 = [bounds1.topLeft, bounds1.topRight, bounds1.bottomRight, bounds1.bottomLeft]; let ps2 = [bounds2.topLeft, bounds2.topRight, bounds2.bottomRight, bounds2.bottomLeft]; - for (let i = 0; i < 4; i++) { for (let j = (i + 1) % 2; j < 4; j += 2) { // iterate over the perpendicular lines let a = ps1[i]; let b = ps1[(i + 1) % 4]; let c = ps2[j]; - let d = ps2[(j + 1) % 4]; // Perpendicular lines will intersect, we'll use parametric line intersection + let d = ps2[(j + 1) % 4]; + + // Perpendicular lines will intersect, we'll use parametric line intersection // = a + (b - a)t1 // = c + (d - c)t2 //a + (b - a)t1 = c + (d - c)t2 //t1(b - a) = (c + (d - c)t2 - a) //(a - c)/(d - c) = t2 - let t1, t2; - if (a.x === b.x) { t2 = (a.x - c.x) / (d.x - c.x); t1 = (c.y + (d.y - c.y) * t2 - a.y) / (b.y - a.y); @@ -57753,7 +56829,6 @@ Wick.Clip = class extends Wick.Tickable { t2 = (a.y - c.y) / (d.y - c.y); t1 = (c.x + (d.x - c.x) * t2 - a.x) / (b.x - a.x); } - if (0 <= t1 && t1 <= 1 && 0 <= t2 && t2 <= 1) { result.intersections.push({ x: a.x + (b.x - a.x) * t1, @@ -57763,39 +56838,37 @@ Wick.Clip = class extends Wick.Tickable { } } } - return result; } else { return null; } - } // Return whether triangle p1 p2 p3 is clockwise (in screen space, - // means counterclockwise in a normal space with y axis pointed up) - + } + // Return whether triangle p1 p2 p3 is clockwise (in screen space, + // means counterclockwise in a normal space with y axis pointed up) cw(x1, y1, x2, y2, x3, y3) { const cw = (y3 - y1) * (x2 - x1) - (y2 - y1) * (x3 - x1); return cw >= 0; // colinear ? } + /** * Perform convex hull hit test with other clip. * @param {Wick.Clip} other - the clip to hit test with * @param {object} options - Hit test options * @returns {object} Hit information */ - - convexHits(other, options) { // Efficient check first let bounds1 = this.absoluteBounds; - let bounds2 = other.absoluteBounds; // TODO: write intersects so we don't rely on paper Rectangle objects - + let bounds2 = other.absoluteBounds; + // TODO: write intersects so we don't rely on paper Rectangle objects if (!bounds1.intersects(bounds2)) { return null; } - let c1 = bounds1.center; - let c2 = bounds2.center; // clockwise arrays of points in format [[x1, y1], [x2, y2], ...] + let c2 = bounds2.center; + // clockwise arrays of points in format [[x1, y1], [x2, y2], ...] let hull1 = this.convexHull; let hull2 = other.convexHull; let finished1 = false; @@ -57803,15 +56876,17 @@ Wick.Clip = class extends Wick.Tickable { let i1 = hull1.length - 1; let i2 = hull2.length - 1; let intersections = []; - let n = 0; // Algorithm from https://www.bowdoin.edu/~ltoma/teaching/cs3250-CompGeom/spring17/Lectures/cg-convexintersection.pdf - + let n = 0; + // Algorithm from https://www.bowdoin.edu/~ltoma/teaching/cs3250-CompGeom/spring17/Lectures/cg-convexintersection.pdf while ((!finished1 || !finished2) && n <= 2 * (hull1.length + hull2.length)) { - n++; // line segments A is ab, B is cd - + n++; + // line segments A is ab, B is cd let a = hull1[i1], - b = hull1[((i1 - 1) % hull1.length + hull1.length) % hull1.length], - c = hull2[i2], - d = hull2[((i2 - 1) % hull2.length + hull2.length) % hull2.length]; //Use parametric line intersection + b = hull1[((i1 - 1) % hull1.length + hull1.length) % hull1.length], + c = hull2[i2], + d = hull2[((i2 - 1) % hull2.length + hull2.length) % hull2.length]; + + //Use parametric line intersection // = a + (b - a)t1 // = c + (d - c)t2 //a + (b - a)t1 = c + (d - c)t2 @@ -57819,24 +56894,19 @@ Wick.Clip = class extends Wick.Tickable { //a.y + (b.y - a.y) * (c.x + (d.x - c.x)t2 - a.x) / (b.x - a.x) = c.y + (d.y - c.y)t2 //t2((b.y - a.y)(d.x - c.x)/(b.x - a.x) - (d.y - c.y)) = c.y - a.y - (b.y - a.y)*(c.x - a.x)/(b.x - a.x) //t2 = (c.y - a.y - (b.y - a.y)*(c.x - a.x)/(b.x - a.x)) / ((b.y - a.y)(d.x - c.x)/(b.x - a.x) - (d.y - c.y)) - let t2 = (c[1] - a[1] - (b[1] - a[1]) * (c[0] - a[0]) / (b[0] - a[0])) / ((b[1] - a[1]) * (d[0] - c[0]) / (b[0] - a[0]) - d[1] + c[1]); let t1 = (c[0] + (d[0] - c[0]) * t2 - a[0]) / (b[0] - a[0]); - if (0 <= t1 && t1 <= 1 && 0 <= t2 && t2 <= 1) { intersections.push({ x: a[0] + (b[0] - a[0]) * t1, y: a[1] + (b[1] - a[1]) * t1 }); } - let APointingToB = t1 > 1; let BPointingToA = t2 > 1; - if (BPointingToA && !APointingToB) { // Advance B i2 -= 1; - if (i2 < 0) { finished2 = true; i2 += hull2.length; @@ -57844,7 +56914,6 @@ Wick.Clip = class extends Wick.Tickable { } else if (APointingToB && !BPointingToA) { // Advance A i1 -= 1; - if (i1 < 0) { finished1 = true; i1 += hull1.length; @@ -57854,7 +56923,6 @@ Wick.Clip = class extends Wick.Tickable { if (this.cw(a[0], a[1], b[0], b[1], d[0], d[1])) { // Advance B i2 -= 1; - if (i2 < 0) { finished2 = true; i2 += hull2.length; @@ -57862,21 +56930,18 @@ Wick.Clip = class extends Wick.Tickable { } else { // Advance A i1 -= 1; - if (i1 < 0) { finished1 = true; i1 += hull1.length; } } } - } // Ok, we have all the intersections now - - + } + // Ok, we have all the intersections now let avgIntersection = { x: 0, y: 0 }; - if (intersections.length === 0) { avgIntersection.x = bounds1.width < bounds2.width ? c1.x : c2.x; avgIntersection.y = bounds1.width < bounds2.width ? c1.y : c2.y; @@ -57885,25 +56950,21 @@ Wick.Clip = class extends Wick.Tickable { avgIntersection.x += intersections[i].x; avgIntersection.y += intersections[i].y; } - avgIntersection.x /= intersections.length; avgIntersection.y /= intersections.length; } - let result = {}; - if (options.intersections) { result.intersections = intersections; } - if (options.offset) { // Calculate offset by taking the center of mass of the intersection, call it P, // get the radius from P on this convex hull in the direction // from this center to that center, // Then, the offset is a vector in the direction from that center to this center // with magnitude of that radius - let targetTheta = Math.atan2(c2.y - c1.y, c2.x - c1.x); //from c1 to c2 + let targetTheta = Math.atan2(c2.y - c1.y, c2.x - c1.x); //from c1 to c2 let r = this.radiusAtPointInDirection(hull1, avgIntersection, targetTheta); targetTheta = (targetTheta + Math.PI) % (2 * Math.PI); r += this.radiusAtPointInDirection(hull2, avgIntersection, targetTheta); @@ -57915,27 +56976,23 @@ Wick.Clip = class extends Wick.Tickable { result.offsetX = directionX; result.offsetY = directionY; } - if (options.overlap) { //same as offset except instead of center to center, //we will move perpendicular to the best fit line //of the intersection points - let directionX, directionY; + let directionX, directionY; if (intersections.length < 2) { directionX = c2.x - c1.x; directionY = c2.y - c1.y; } else { let max_d = 0; - for (let i = 1; i < intersections.length; i++) { let d = (intersections[i].y - intersections[0].y) * (intersections[i].y - intersections[0].y) + (intersections[i].x - intersections[0].x) * (intersections[i].x - intersections[0].x); - if (d > max_d) { max_d = d; directionX = -(intersections[i].y - intersections[0].y); directionY = intersections[i].x - intersections[0].x; - if (directionX * (c1.x - avgIntersection.x) + directionY * (c1.y - avgIntersection.y) > 0) { directionX = -directionX; directionY = -directionY; @@ -57943,7 +57000,6 @@ Wick.Clip = class extends Wick.Tickable { } } } - let targetTheta = Math.atan2(directionY, directionX); let r = this.radiusAtPointInDirection(hull1, avgIntersection, targetTheta); targetTheta = (targetTheta + Math.PI) % (2 * Math.PI); @@ -57951,22 +57007,20 @@ Wick.Clip = class extends Wick.Tickable { let r2 = this.radiusAtPointInDirection(hull1, avgIntersection, targetTheta); targetTheta = (targetTheta + Math.PI) % (2 * Math.PI); r2 += this.radiusAtPointInDirection(hull2, avgIntersection, targetTheta); - if (r2 < r) { r = r2; directionX *= -1; directionY *= -1; } - let mag = Math.sqrt(directionX * directionX + directionY * directionY); directionX *= -r / mag; directionY *= -r / mag; result.overlapX = directionX; result.overlapY = directionY; } - return result; } + /** * Casts a ray from p in the direction targetTheta and intersects it with the hull ch, * returns the distance from p to the surface of ch. @@ -57975,26 +57029,22 @@ Wick.Clip = class extends Wick.Tickable { * @param {number} targetTheta - the direction of the ray * @returns {number} the distance to the surface of the convex hull from the point in the direction theta */ - - radiusAtPointInDirection(ch, p, targetTheta) { let minThetaDiff = Infinity; let index; - for (let i = 0; i < ch.length; i++) { let theta = Math.atan2(ch[i][1] - p.y, ch[i][0] - p.x); let thetaDiff = ((targetTheta - theta) % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI); //positive mod - if (thetaDiff < minThetaDiff) { minThetaDiff = thetaDiff; index = i; } } - let a = ch[index]; let b = ch[(index + 1) % ch.length]; let c = [p.x, p.y]; - let d = [p.x + 100 * Math.cos(targetTheta), p.y + 100 * Math.sin(targetTheta)]; //Use parametric line intersection + let d = [p.x + 100 * Math.cos(targetTheta), p.y + 100 * Math.sin(targetTheta)]; + //Use parametric line intersection // = a + (b - a)t1 // = c + (d - c)t2 //a + (b - a)t1 = c + (d - c)t2 @@ -58002,46 +57052,39 @@ Wick.Clip = class extends Wick.Tickable { //a.y + (b.y - a.y) * (c.x + (d.x - c.x)t2 - a.x) / (b.x - a.x) = c.y + (d.y - c.y)t2 //t2((b.y - a.y)(d.x - c.x)/(b.x - a.x) - (d.y - c.y)) = c.y - a.y - (b.y - a.y)*(c.x - a.x)/(b.x - a.x) //t2 = (c.y - a.y - (b.y - a.y)*(c.x - a.x)/(b.x - a.x)) / ((b.y - a.y)(d.x - c.x)/(b.x - a.x) - (d.y - c.y)) - let t2 = (c[1] - a[1] - (b[1] - a[1]) * (c[0] - a[0]) / (b[0] - a[0])) / ((b[1] - a[1]) * (d[0] - c[0]) / (b[0] - a[0]) - d[1] + c[1]); let t1 = (c[0] + (d[0] - c[0]) * t2 - a[0]) / (b[0] - a[0]); return Math.hypot(a[0] + (b[0] - a[0]) * t1 - p.x, a[1] + (b[1] - a[1]) * t1 - p.y); } + /** * Perform hit test with other clip. * @param {Wick.Clip} other - the clip to hit test with * @param {object} options - Hit test options * @returns {object} Hit information */ - - hits(other, options) { // Get hit options - let finalOptions = { ...this.project.hitTestOptions + let finalOptions = { + ...this.project.hitTestOptions }; - if (options) { if (options.mode === 'CIRCLE' || options.mode === 'RECTANGLE' || options.mode === 'CONVEX') { finalOptions.mode = options.mode; } - if (typeof options.offset === "boolean") { finalOptions.offset = options.offset; } - if (typeof options.overlap === "boolean") { finalOptions.overlap = options.overlap; } - if (typeof options.intersections === "boolean") { finalOptions.intersections = options.intersections; } - if (options.radius) { finalOptions.radius = options.radius; } } - if (finalOptions.mode === 'CIRCLE') { return this.circleHits(other, finalOptions); } else if (finalOptions.mode === 'CONVEX') { @@ -58050,43 +57093,39 @@ Wick.Clip = class extends Wick.Tickable { return this.rectangleHits(other, finalOptions); } } + /** * Returns true if this clip collides with another clip. * @param {Wick.Clip} other - The other clip to check collision with. * @returns {boolean} True if this clip collides the other clip. */ - - hitTest(other) { // TODO: write intersects so we don't rely on paper Rectangle objects return this.absoluteBounds.intersects(other.absoluteBounds); } + /** * The bounding box of the clip. * @type {object} */ - - get bounds() { // TODO: Refactor so that getting bounds does not rely on the view return this.view.bounds; } - get absoluteBounds() { // TODO: Refactor so that getting bounds does not rely on the view return this.view.absoluteBounds; } - get points() { // TODO: Refactor so that does not rely on the view return this.view.points; } - get radius() { // Use length of half diagonal of bounding box let b = this.absoluteBounds; - return Math.sqrt(b.width * b.width + b.height * b.height) / 2 / Math.sqrt(2); // Alternative: use largest distance from center to a point on the object + return Math.sqrt(b.width * b.width + b.height * b.height) / 2 / Math.sqrt(2); + // Alternative: use largest distance from center to a point on the object /* let center = this.absoluteBounds.center; let points = this.points; @@ -58099,16 +57138,16 @@ Wick.Clip = class extends Wick.Tickable { } return Math.sqrt(max_r); */ - } // Gives clockwise in screen space, which is ccw in regular axes - + } + // Gives clockwise in screen space, which is ccw in regular axes get convexHull() { - let points = this.points; // Infinity gets us the convex hull + let points = this.points; + // Infinity gets us the convex hull let ch = hull(points, Infinity); let removedDuplicates = []; let epsilon = 0.01; - for (let i = 0; i < ch.length; i++) { if (removedDuplicates.length > 0) { if ((Math.abs(ch[i][0] - removedDuplicates[removedDuplicates.length - 1][0]) > epsilon || Math.abs(ch[i][1] - removedDuplicates[removedDuplicates.length - 1][1]) > epsilon) && (Math.abs(ch[i][0] - removedDuplicates[0][0]) > epsilon || Math.abs(ch[i][1] - removedDuplicates[0][1]) > epsilon)) { @@ -58118,159 +57157,134 @@ Wick.Clip = class extends Wick.Tickable { removedDuplicates.push(ch[i]); } } - return removedDuplicates; } + /** * The X position of the clip. * @type {number} */ - - get x() { return this.transformation.x; } - set x(x) { this.transformation.x = x; } + /** * The Y position of the clip. * @type {number} */ - - get y() { return this.transformation.y; } - set y(y) { this.transformation.y = y; } + /** * The X scale of the clip. * @type {number} */ - - get scaleX() { return this.transformation.scaleX; } - set scaleX(scaleX) { if (scaleX === 0) scaleX = 0.001; // Protects against NaN issues - this.transformation.scaleX = scaleX; } + /** * The Y scale of the clip. * @type {number} */ - - get scaleY() { return this.transformation.scaleY; } - set scaleY(scaleY) { if (scaleY === 0) scaleY = 0.001; // Protects against NaN issues - this.transformation.scaleY = scaleY; } + /** * The width of the clip. * @type {number} */ - - get width() { return this.isRoot ? this.project.width : this.bounds.width * this.scaleX; } - set width(width) { this.scaleX = width / this.width * this.scaleX; } + /** * The height of the clip. * @type {number} */ - - get height() { return this.isRoot ? this.project.height : this.bounds.height * this.scaleY; } - set height(height) { this.scaleY = height / this.height * this.scaleY; } + /** * The rotation of the clip. * @type {number} */ - - get rotation() { return this.transformation.rotation; } - set rotation(rotation) { this.transformation.rotation = rotation; } + /** * The opacity of the clip. * @type {number} */ - - get opacity() { return this.transformation.opacity; } - set opacity(opacity) { opacity = Math.min(1, opacity); opacity = Math.max(0, opacity); this.transformation.opacity = opacity; } + /** * Copy this clip, and add the copy to the same frame as the original clip. * @returns {Wick.Clip} the result of the clone. */ - - clone() { var clone = this.copy(); clone.identifier = null; this.parentFrame.addClip(clone); - this._clones.push(clone); - clone._isClone = true; clone._sourceClipUUID = this.uuid; return clone; } + /** * An array containing all objects that were created by calling clone() on this Clip. * @type {Wick.Clip[]} */ - - get clones() { return this._clones; } + /** * This is a stopgap to prevent users from using setText with a Clip. */ - - setText() { throw new Error('setText() can only be used with text objects.'); } + /** * The list of parents, grandparents, grand-grandparents...etc of the clip. * @returns {Wick.Clip[]} Array of all parents */ - - get lineage() { if (this.isRoot) { return [this]; @@ -58278,50 +57292,50 @@ Wick.Clip = class extends Wick.Tickable { return [this].concat(this.parentClip.lineage); } } + /** * Add a placeholder path to this clip to ensure the Clip is always selectable when rendered. */ - - ensureActiveFrameIsContentful() { // Ensure layer exists var firstLayerExists = this.timeline.activeLayer; - if (!firstLayerExists) { this.timeline.addLayer(new Wick.Layer()); - } // Ensure active frame exists - + } + // Ensure active frame exists var playheadPosition = this.timeline.playheadPosition; var activeFrameExists = this.timeline.getFramesAtPlayheadPosition(playheadPosition).length > 0; - if (!activeFrameExists) { this.timeline.activeLayer.addFrame(new Wick.Frame({ start: playheadPosition })); - } // Clear placeholders - + } + // Clear placeholders var frame = this.timeline.getFramesAtPlayheadPosition(playheadPosition)[0]; frame.paths.forEach(path => { if (!path.isPlaceholder) return; path.remove(); - }); // Check if active frame is contentful + }); + // Check if active frame is contentful var firstFramesAreContentful = false; this.timeline.getFramesAtPlayheadPosition(playheadPosition).forEach(frame => { if (frame.contentful) { firstFramesAreContentful = true; } - }); // Ensure active frame is contentful + }); + // Ensure active frame is contentful if (!firstFramesAreContentful) { // Clear placeholders var frame = this.timeline.getFramesAtPlayheadPosition(playheadPosition)[0]; frame.paths.forEach(path => { path.remove(); - }); // Generate crosshair + }); + // Generate crosshair var size = Wick.View.Clip.PLACEHOLDER_SIZE; var line1 = new paper.Path.Line({ from: [0, -size], @@ -58345,27 +57359,20 @@ Wick.Clip = class extends Wick.Tickable { })); } } - _onInactive() { super._onInactive(); - this._tickChildren(); } - _onActivated() { super._onActivated(); - this._tickChildren(); - if (this.animationType === 'playOnce') { this.playedOnce = false; this.timeline.playheadPosition = 1; } } - _onActive() { super._onActive(); - if (this.animationType === 'loop') { this.timeline.advance(); } else if (this.animationType === 'single') { @@ -58379,20 +57386,15 @@ Wick.Clip = class extends Wick.Tickable { } } } - if (this.isSynced) { this.timeline.playheadPosition = this.syncFrame; } - this._tickChildren(); } - _onDeactivated() { super._onDeactivated(); - this._tickChildren(); } - _tickChildren() { var childError = null; this.timeline.frames.forEach(frame => { @@ -58401,23 +57403,21 @@ Wick.Clip = class extends Wick.Tickable { }); return childError; } - _attachChildClipReferences() { this.timeline.activeFrames.forEach(frame => { frame.clips.forEach(clip => { if (clip.identifier) { this[clip.identifier] = clip; - clip._attachChildClipReferences(); } - }); // Dynamic text paths can be accessed by their identifiers. + }); + // Dynamic text paths can be accessed by their identifiers. frame.dynamicTextPaths.forEach(path => { this[path.identifier] = path; }); }); } - }; /* * Copyright 2020 WICKLETS LLC @@ -58465,38 +57465,29 @@ Wick.Button = class extends Wick.Clip { this.removeScript('default'); this.addScript('mouseclick', ''); } - _serialize(args) { var data = super._serialize(args); - return data; } - _deserialize(data) { super._deserialize(data); } - get classname() { return 'Button'; } - _onInactive() { return super._onInactive(); } - _onActivated() { var error = super._onActivated(); - this.timeline.stop(); this.timeline.playheadPosition = 1; return error; } - _onActive() { this.timeline.gotoFrame(1); var frame2Exists = this.timeline.getFramesAtPlayheadPosition(2).length > 0; var frame3Exists = this.timeline.getFramesAtPlayheadPosition(3).length > 0; - if (this._mouseState === 'over') { if (frame2Exists) { this.timeline.gotoFrame(2); @@ -58508,17 +57499,13 @@ Wick.Button = class extends Wick.Clip { this.timeline.gotoFrame(2); } } - var error = super._onActive(); - if (error) return error; return null; } - _onDeactivated() { super._onDeactivated(); } - }; /* * Copyright 2020 WICKLETS LLC @@ -58538,205 +57525,183 @@ Wick.Button = class extends Wick.Clip { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.Tool = class { static get DOUBLE_CLICK_TIME() { return 300; } - static get DOUBLE_CLICK_MAX_DISTANCE() { return 20; } + /** * Creates a new Wick Tool. */ - - constructor() { - this.paperTool = new this.paper.Tool(); // Attach onActivate event + this.paperTool = new this.paper.Tool(); + // Attach onActivate event this.paperTool.onActivate = e => { this.onActivate(e); - }; // Attach onDeactivate event - + }; + // Attach onDeactivate event this.paperTool.onDeactivate = e => { this.onDeactivate(e); - }; // Attach mouse move event - + }; + // Attach mouse move event this.paperTool.onMouseMove = e => { this.onMouseMove(e); - }; // Attach mouse down + double click event - + }; + // Attach mouse down + double click event this.paperTool.onMouseDown = e => { if (this.doubleClickEnabled && this._lastMousedownTimestamp !== null && e.timeStamp - this._lastMousedownTimestamp < Wick.Tool.DOUBLE_CLICK_TIME && e.point.subtract(this._lastMousedownPoint).length < Wick.Tool.DOUBLE_CLICK_MAX_DISTANCE) { this.onDoubleClick(e); } else { this.onMouseDown(e); } - this._lastMousedownTimestamp = e.timeStamp; this._lastMousedownPoint = e.point; - }; // Attach key events - + }; + // Attach key events this.paperTool.onKeyDown = e => { this.onKeyDown(e); }; - this.paperTool.onKeyUp = e => { this.onKeyUp(e); - }; // Attach mouse move event - + }; + // Attach mouse move event this.paperTool.onMouseDrag = e => { this.onMouseDrag(e); - }; // Attach mouse up event - + }; + // Attach mouse up event this.paperTool.onMouseUp = e => { this.onMouseUp(e); }; - this._eventCallbacks = {}; this._lastMousedownTimestamp = null; } + /** * The paper.js scope to use. */ - - get paper() { return Wick.View.paperScope; } + /** * The CSS cursor to display for this tool. */ - - get cursor() { console.warn("Warning: Tool is missing a cursor!"); } + /** * Called when the tool is activated */ - - onActivate(e) {} + /** * Called when the tool is deactivated (another tool is activated) */ - - onDeactivate(e) {} + /** * Called when the mouse moves and the tool is active. */ - - onMouseMove(e) { this.setCursor(this.cursor); } + /** * Called when the mouse clicks the paper.js canvas and this is the active tool. */ - - onMouseDown(e) {} + /** * Called when the mouse is dragged on the paper.js canvas and this is the active tool. */ - - onMouseDrag(e) {} + /** * Called when the mouse is clicked on the paper.js canvas and this is the active tool. */ - - onMouseUp(e) {} + /** * Called when the mouse double clicks on the paper.js canvas and this is the active tool. */ - - onDoubleClick(e) {} + /** * Called when a key is pressed and this is the active tool. */ - - onKeyDown(e) {} + /** * Called when a key is released and this is the active tool. */ - - onKeyUp(e) {} + /** * Should reset the state of the tool. */ - - reset() {} + /** * Activates this tool in paper.js. */ - - activate() { this.paperTool.activate(); } + /** * Sets the cursor of the paper.js canvas that the tool belongs to. * @param {string} cursor - a CSS cursor style */ - - setCursor(cursor) { this.paper.view._element.style.cursor = cursor; } + /** * Attach a function to get called when an event happens. * @param {string} eventName - the name of the event * @param {function} fn - the function to call when the event is fired */ - - on(eventName, fn) { this._eventCallbacks[eventName] = fn; } + /** * Call the functions attached to a given event. * @param {string} eventName - the name of the event to fire * @param {object} e - (optional) an object to attach some data to, if needed * @param {string} actionName - Name of the action committed. */ - - fireEvent({ eventName, e, actionName }) { if (!e) e = {}; - if (!e.layers) { e.layers = [this.paper.project.activeLayer]; } - var fn = this._eventCallbacks[eventName]; fn && fn(e, actionName); } + /** * * @param {paper.Color} color - the color of the cursor * @param {number} size - the width of the cursor image to generate * @param {boolean} transparent - if set to true, color is ignored */ - - createDynamicCursor(color, size, transparent) { var radius = size / 2; var canvas = document.createElement("canvas"); @@ -58749,7 +57714,6 @@ Wick.Tool = class { context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false); context.strokeStyle = transparent ? 'black' : invert(color); context.stroke(); - if (transparent) { context.beginPath(); context.arc(centerX, centerY, radius - 1, 0, 2 * Math.PI, false); @@ -58761,59 +57725,50 @@ Wick.Tool = class { context.fillStyle = color; context.fill(); } - return 'url(' + canvas.toDataURL() + ') ' + (radius + 1) + ' ' + (radius + 1) + ',default'; } + /** * Get a tool setting from the project. See Wick.ToolSettings for all options * @param {string} name - the name of the setting to get */ - - getSetting(name) { return this.project.toolSettings.getSetting(name); } + /** * Does this tool have a double click action? (override this in classes that extend Wick.Tool) * @type {boolean} */ - - get doubleClickEnabled() { return true; } + /** * Adds a paper.Path to the active frame's paper.Layer. * @param {paper.Path} path - the path to add * @param {Wick.Frame} frame - (optional) the frame to add the path to. */ - - addPathToProject(path, frame) { // Avoid adding empty paths if (!path) { return; } - if (path instanceof paper.Path && path.segments.length === 0) { return; } - if (path instanceof paper.CompoundPath && path.children.length === 0) { return; } - if (!this.project.activeFrame) { // Automatically add a frame is there isn't one this.project.insertBlankFrame(); this.project.view.render(); } - if (!path) { console.error("Warning: addPathToProject: path is null/undefined"); return; } - if (frame && frame !== this.project.activeFrame) { /* If the path must be added to a frame other than the active frame, * convert the paper.js path into a Wick path and add it to the given frame. */ @@ -58830,7 +57785,6 @@ Wick.Tool = class { this.paper.project.activeLayer.addChild(path); } } - }; Wick.Tools = {}; /* @@ -58851,19 +57805,18 @@ Wick.Tools = {}; * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.Tools.Brush = class extends Wick.Tool { static get CROQUIS_WAIT_AMT_MS() { return 100; } - get doubleClickEnabled() { return false; } + /** * Creates the brush tool. */ - - constructor() { super(); this.name = 'brush'; @@ -58878,25 +57831,24 @@ Wick.Tools.Brush = class extends Wick.Tool { this.lastPressure = null; this.errorOccured = false; this._isInProgress = false; - this._croquisStartTimeout = null; // These are used to crop the final path image. + this._croquisStartTimeout = null; + // These are used to crop the final path image. this.strokeBounds = new paper.Rectangle(); this._lastMousePoint = new paper.Point(0, 0); - this._lastMousePressure = 1; // The frame that the brush started the current stroke on. + this._lastMousePressure = 1; + // The frame that the brush started the current stroke on. this._currentDrawingFrame = null; } - - get cursor() {// the brush cursor is done in a custom way using _regenCursor(). + get cursor() { + // the brush cursor is done in a custom way using _regenCursor(). } - get isDrawingTool() { return true; } - onActivate(e) { if (this._isInProgress) this.finishStrokeEarly(); - if (!this.croquis) { this.croquis = new Croquis(); this.croquis.setCanvasSize(500, 500); @@ -58916,96 +57868,77 @@ Wick.Tools.Brush = class extends Wick.Tool { this.croquisDOMElement.style.display = 'block'; this.croquisDOMElement.style.pointerEvents = 'none'; } - this._isInProgress = false; this._lastMousePoint = new paper.Point(0, 0); this._lastMousePressure = 1; } - onDeactivate(e) { // This prevents croquis from leaving stuck brush strokes on the screen. this.finishStrokeEarly(); } - onMouseMove(e) { super.onMouseMove(e); - this._updateCanvasAttributes(); - this._regenCursor(); } - onMouseDown(e) { if (this._isInProgress) this.discard(); this._currentDrawingFrame = this.project.activeFrame; clearTimeout(this._croquisStartTimeout); this._isInProgress = true; + this._updateCanvasAttributes(); - this._updateCanvasAttributes(); // Update croquis params - - + // Update croquis params this.croquisBrush.setSize(this._getRealBrushSize()); this.croquisBrush.setColor(this.getSetting('fillColor').hex); this.croquisBrush.setSpacing(this.BRUSH_POINT_SPACING); this.croquis.setToolStabilizeLevel(this.BRUSH_STABILIZER_LEVEL); this.croquis.setToolStabilizeWeight(this.getSetting('brushStabilizerWeight') / 100.0 + 0.3); - this.croquis.setToolStabilizeInterval(1); // Forward mouse event to croquis canvas + this.croquis.setToolStabilizeInterval(1); + // Forward mouse event to croquis canvas var point = this._croquisToPaperPoint(e.point); - this._updateStrokeBounds(point); - try { this._updateLastMouseState(point, this.pressure); - this.croquis.down(point.x, point.y, this.pressure); } catch (e) { this.handleBrushError(e); return; } } - onMouseDrag(e) { - if (!this._isInProgress) return; // Forward mouse event to croquis canvas + if (!this._isInProgress) return; + // Forward mouse event to croquis canvas var point = this._croquisToPaperPoint(e.point); - this._updateStrokeBounds(point); - try { this._updateLastMouseState(point, this.pressure); - this.croquis.move(point.x, point.y, this.pressure); } catch (e) { this.handleBrushError(e); return; } - this.lastPressure = this.pressure; } - onMouseUp(e) { if (!this._isInProgress) return; this._isInProgress = false; - var point = this._croquisToPaperPoint(e.point); - this._calculateStrokeBounds(point); - try { this.croquis.up(point.x, point.y, this.lastPressure); } catch (e) { this.handleBrushError(e); return; } - this._potraceCroquisCanvas(point); } + /** * The current amount of pressure applied to the paper js canvas this tool belongs to. */ - - get pressure() { if (this.getSetting('pressureEnabled')) { var pressure = this.paper.view.pressure; @@ -59014,176 +57947,161 @@ Wick.Tools.Brush = class extends Wick.Tool { return 1; } } + /** * Croquis throws a lot of errrors. This is a helpful function to handle those errors gracefully. */ - - handleBrushError(e) { this._isInProgress = false; this.croquis.clearLayer(); - if (!this.errorOccured) { console.error("Brush error"); console.error(e); } - this.errorOccured = true; } + /** * Is the brush currently making a stroke? * @type {boolean} */ - - isInProgress() { return this._isInProgress; } + /** * Discard the current brush stroke. */ - - discard() { if (!this._isInProgress) return; - this._isInProgress = false; // "Give up" on the current stroke by forcing a mouseup + this._isInProgress = false; - this.croquis.up(this._lastMousePoint.x, this._lastMousePoint.y, this._lastMousePressure); // Clear the current croquis canvas + // "Give up" on the current stroke by forcing a mouseup + this.croquis.up(this._lastMousePoint.x, this._lastMousePoint.y, this._lastMousePressure); + // Clear the current croquis canvas setTimeout(() => { this.croquis.clearLayer(); }, 10); } + /** * Force the current stroke to be finished, and add the stroke to the project. */ - - finishStrokeEarly() { if (!this._isInProgress) return; - this._isInProgress = false; // Hide the croquis canvas so that the current stroke is never seen on the new frame. + this._isInProgress = false; - this.croquisDOMElement.style.opacity = 0; // "Give up" on the current stroke by forcing a mouseup + // Hide the croquis canvas so that the current stroke is never seen on the new frame. + this.croquisDOMElement.style.opacity = 0; - this.croquis.up(this._lastMousePoint.x, this._lastMousePoint.y, this._lastMousePressure); // Add path to project + // "Give up" on the current stroke by forcing a mouseup + this.croquis.up(this._lastMousePoint.x, this._lastMousePoint.y, this._lastMousePressure); + // Add path to project this._calculateStrokeBounds(this._lastMousePoint); - this._potraceCroquisCanvas(this._lastMousePoint); } - /* Generate a new circle cursor based on the brush size. */ - + /* Generate a new circle cursor based on the brush size. */ _regenCursor() { var size = this._getRealBrushSize(); - var color = this.getSetting('fillColor').hex; this.cachedCursor = this.createDynamicCursor(color, size, this.getSetting('pressureEnabled')); this.setCursor(this.cachedCursor); } - /* Get the actual pixel size of the brush to send to Croquis. */ - + /* Get the actual pixel size of the brush to send to Croquis. */ _getRealBrushSize() { var size = this.getSetting('brushSize') + 1; - if (!this.getSetting('relativeBrushSize')) { size *= this.paper.view.zoom; } - return size; } - /* Update Croquis and the div containing croquis to reflect all current options. */ - + /* Update Croquis and the div containing croquis to reflect all current options. */ _updateCanvasAttributes() { if (!this.paper.view._element.parentElement) { return; - } // Update croquis element and pressure options - + } + // Update croquis element and pressure options if (!this.paper.view._element.parentElement.contains(this.croquisDOMElement)) { this.paper.view.enablePressure(); - this.paper.view._element.parentElement.appendChild(this.croquisDOMElement); - } // Update croquis element canvas size - + } + // Update croquis element canvas size if (this.croquis.getCanvasWidth() !== this.paper.view._element.width || this.croquis.getCanvasHeight() !== this.paper.view._element.height) { this.croquis.setCanvasSize(this.paper.view._element.width, this.paper.view._element.height); - } // Fake brush opacity in croquis by changing the opacity of the croquis canvas - + } + // Fake brush opacity in croquis by changing the opacity of the croquis canvas this.croquisDOMElement.style.opacity = this.getSetting('fillColor').a; } - /* Convert a point in Croquis' canvas space to paper.js's canvas space. */ - + /* Convert a point in Croquis' canvas space to paper.js's canvas space. */ _croquisToPaperPoint(croquisPoint) { var paperPoint = this.paper.view.projectToView(croquisPoint.x, croquisPoint.y); return paperPoint; } - /* Used for calculating the crop amount for potrace. */ - + /* Used for calculating the crop amount for potrace. */ _resetStrokeBounds(point) { this.strokeBounds = new paper.Rectangle(point.x, point.y, 1, 1); } - /* Used for calculating the crop amount for potrace. */ - + /* Used for calculating the crop amount for potrace. */ _updateStrokeBounds(point) { this.strokeBounds = this.strokeBounds.include(point); } - /* Used for saving information on the mouse (croquis does not save this.) */ - + /* Used for saving information on the mouse (croquis does not save this.) */ _updateLastMouseState(point, pressure) { this._lastMousePoint = new paper.Point(point.x, point.y); this._lastMousePressure = this.pressure; } - _calculateStrokeBounds(point) { // Forward mouse event to croquis canvas - this._updateStrokeBounds(point); // This prevents cropping out edges of the brush stroke - - + this._updateStrokeBounds(point); + // This prevents cropping out edges of the brush stroke this.strokeBounds = this.strokeBounds.expand(this._getRealBrushSize()); } - /* Create a paper.js path by potracing the croquis canvas, and add the resulting path to the project. */ - + /* Create a paper.js path by potracing the croquis canvas, and add the resulting path to the project. */ _potraceCroquisCanvas(point) { this.errorOccured = false; - var strokeBounds = this.strokeBounds.clone(); // Attempting to draw with a transparent fill color. Throw an error. + var strokeBounds = this.strokeBounds.clone(); + // Attempting to draw with a transparent fill color. Throw an error. if (this.getSetting('fillColor').a === 0) { this.handleBrushError('transparentColor'); this.project.errorOccured("Fill Color is Transparent!"); return; - } // Give croquis just a little bit to get the canvas ready... - + } + // Give croquis just a little bit to get the canvas ready... this._croquisStartTimeout = setTimeout(() => { // Retrieve Croquis canvas var canvas = this.paper.view._element.parentElement.getElementsByClassName('croquis-layer-canvas')[1]; - if (!canvas) { console.warn("Croquis canvas was not found in the canvas container. Something very bad has happened."); this.handleBrushError('misingCroquisCanvas'); return; - } // Rip image data out of Croquis.js canvas - // (and crop out empty space using strokeBounds - this massively speeds up potrace) - + } + // Rip image data out of Croquis.js canvas + // (and crop out empty space using strokeBounds - this massively speeds up potrace) var croppedCanvas = document.createElement("canvas"); var croppedCanvasCtx = croppedCanvas.getContext("2d"); croppedCanvas.width = strokeBounds.width; croppedCanvas.height = strokeBounds.height; if (strokeBounds.x < 0) strokeBounds.x = 0; if (strokeBounds.y < 0) strokeBounds.y = 0; - croppedCanvasCtx.drawImage(canvas, strokeBounds.x, strokeBounds.y, strokeBounds.width, strokeBounds.height, 0, 0, croppedCanvas.width, croppedCanvas.height); // Run potrace and add the resulting path to the project + croppedCanvasCtx.drawImage(canvas, strokeBounds.x, strokeBounds.y, strokeBounds.width, strokeBounds.height, 0, 0, croppedCanvas.width, croppedCanvas.height); + // Run potrace and add the resulting path to the project var svg = potrace.fromImage(croppedCanvas).toSVG(1 / this.POTRACE_RESOLUTION / this.paper.view.zoom); var potracePath = this.paper.project.importSVG(svg); potracePath.fillColor = this.getSetting('fillColor').rgba; @@ -59195,22 +58113,23 @@ Wick.Tools.Brush = class extends Wick.Tool { potracePath.closed = true; potracePath.children[0].closed = true; potracePath.children[0].applyMatrix = true; - var result = potracePath.children[0]; // Do special brush mode action + var result = potracePath.children[0]; + // Do special brush mode action var brushMode = this.getSetting('brushMode'); - if (this._currentDrawingFrame && this._currentDrawingFrame.view) { // Don't apply brush mode if there is no frame to draw on // (the frame is added during addPathToProject) result = this._applyBrushMode(brushMode, result, this._currentDrawingFrame.view.objectsLayer); - } // Done! Add the path to the project - - - this.addPathToProject(result, this._currentDrawingFrame); // We're done potracing using the current croquis canvas, reset the stroke bounds + } - this._resetStrokeBounds(point); // Clear croquis canvas + // Done! Add the path to the project + this.addPathToProject(result, this._currentDrawingFrame); + // We're done potracing using the current croquis canvas, reset the stroke bounds + this._resetStrokeBounds(point); + // Clear croquis canvas this.croquis.clearLayer(); this.fireEvent({ eventName: 'canvasModified', @@ -59218,18 +58137,15 @@ Wick.Tools.Brush = class extends Wick.Tool { }); }, Wick.Tools.Brush.CROQUIS_WAIT_AMT_MS); } - _applyBrushMode(mode, path, layer) { if (!mode) { console.warn('_applyBrushMode: Invalid brush mode: ' + mode); console.warn('Valid brush modes are "inside" and "outside".'); return; } - if (mode === 'none') { return path; } - var booleanOpName = { 'inside': 'intersect', 'outside': 'subtract' @@ -59237,26 +58153,22 @@ Wick.Tools.Brush = class extends Wick.Tool { var mask = null; layer.children.forEach(otherPath => { if (otherPath === mask) return; - if (mask) { var newMask = mask.unite(otherPath); - - if (newMask.children && newMask.children.length === 0 || newMask.segments && newMask.segments.length === 0) {// Ignore boolean ops that result in empty paths + if (newMask.children && newMask.children.length === 0 || newMask.segments && newMask.segments.length === 0) { + // Ignore boolean ops that result in empty paths } else { mask = newMask; } - newMask.remove(); } else { mask = otherPath; } }); - if (!mask) { // Nothing to mask with return path; } - var result = path.clone({ insert: false }); @@ -59264,7 +58176,6 @@ Wick.Tools.Brush = class extends Wick.Tool { result.remove(); return result; } - }; /* * Copyright 2020 WICKLETS LLC @@ -59284,6 +58195,7 @@ Wick.Tools.Brush = class extends Wick.Tool { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.Tools.Cursor = class extends Wick.Tool { /** * Creates a cursor tool. @@ -59311,42 +58223,38 @@ Wick.Tools.Cursor = class extends Wick.Tool { this.selectedItems = []; this.currentCursorIcon = ''; } + /** * Generate the current cursor. * @type {string} */ - - get cursor() { return 'url("' + this.currentCursorIcon + '") 32 32, auto'; } - onActivate(e) { this.selectedItems = []; } - onDeactivate(e) {} - onMouseMove(e) { - super.onMouseMove(e); // Find the thing that is currently under the cursor. + super.onMouseMove(e); - this.hitResult = this._updateHitResult(e); // Update the image being used for the cursor + // Find the thing that is currently under the cursor. + this.hitResult = this._updateHitResult(e); + // Update the image being used for the cursor this._setCursor(this._getCursor()); } - onMouseDown(e) { super.onMouseDown(e); if (!e.modifiers) e.modifiers = {}; this.hitResult = this._updateHitResult(e); - - if (this.hitResult.item && this.hitResult.item.data.isSelectionBoxGUI) {// Clicked the selection box GUI, do nothing + if (this.hitResult.item && this.hitResult.item.data.isSelectionBoxGUI) { + // Clicked the selection box GUI, do nothing } else if (this.hitResult.item && this._isItemSelected(this.hitResult.item)) { // We clicked something that was already selected. // Shift click: Deselect that item if (e.modifiers.shift) { this._deselectItem(this.hitResult.item); - this.fireEvent({ eventName: 'canvasModified', actionName: 'cursorDeselect' @@ -59356,11 +58264,9 @@ Wick.Tools.Cursor = class extends Wick.Tool { if (!e.modifiers.shift) { // Shift click? Keep everything else selected. this._clearSelection(); - } // Clicked an item: select that item - - + } + // Clicked an item: select that item this._selectItem(this.hitResult.item); - this.fireEvent({ eventName: 'canvasModified', actionName: 'cursorSelect' @@ -59370,20 +58276,16 @@ Wick.Tools.Cursor = class extends Wick.Tool { // (don't clear the selection if shift is held, though) if (this._selection.numObjects > 0 && !e.modifiers.shift) { this._clearSelection(); - this.fireEvent({ eventName: 'canvasModified', actionName: 'cursorClearSelect' }); } - this.selectionBox.start(e.point); } } - onDoubleClick(e) { var selectedObject = this._selection.getSelectedObject(); - if (selectedObject && selectedObject instanceof Wick.Clip) { // Double clicked a Clip, set the focus to that Clip. if (this.project.focusTimelineOfSelectedClip()) { @@ -59392,7 +58294,8 @@ Wick.Tools.Cursor = class extends Wick.Tool { actionName: 'cursorFocusTimelineSelected' }); } - } else if (selectedObject && selectedObject instanceof Wick.Path && selectedObject.view.item instanceof paper.PointText) {// Double clicked text, switch to text tool and edit the text item. + } else if (selectedObject && selectedObject instanceof Wick.Path && selectedObject.view.item instanceof paper.PointText) { + // Double clicked text, switch to text tool and edit the text item. // TODO } else if (!selectedObject) { // Double clicked the canvas, leave the current focus. @@ -59404,17 +58307,14 @@ Wick.Tools.Cursor = class extends Wick.Tool { } } } - onMouseDrag(e) { if (!e.modifiers) e.modifiers = {}; this.__isDragging = true; - if (this.hitResult.item && this.hitResult.item.data.isSelectionBoxGUI) { // Update selection drag if (!this._widget.currentTransformation) { this._widget.startTransformation(this.hitResult.item); } - this._widget.updateTransformation(this.hitResult.item, e); } else if (this.selectionBox.active) { // Selection box is being used, update it with a new point @@ -59424,32 +58324,26 @@ Wick.Tools.Cursor = class extends Wick.Tool { if (!this._widget.currentTransformation) { this._widget.startTransformation(this.hitResult.item); } - this._widget.updateTransformation(this.hitResult.item, e); } else { this.__isDragging = false; } } - onMouseUp(e) { if (!e.modifiers) e.modifiers = {}; - if (this.selectionBox.active) { // Finish selection box and select objects touching box (or inside box, if alt is held) this.selectionBox.mode = e.modifiers.alt ? 'contains' : 'intersects'; this.selectionBox.end(e.point); - if (!e.modifiers.shift) { this._selection.clear(); } - let selectables = this.selectionBox.items.filter(item => { return item.data.wickUUID; }); + this._selectItems(selectables); - this._selectItems(selectables); // Only modify the canvas if you actually selected something. - - + // Only modify the canvas if you actually selected something. if (this.selectionBox.items.length > 0) { this.fireEvent({ eventName: 'canvasModified', @@ -59460,9 +58354,7 @@ Wick.Tools.Cursor = class extends Wick.Tool { if (this.__isDragging) { this.__isDragging = false; this.project.tryToAutoCreateTween(); - this._widget.finishTransformation(); - this.fireEvent({ eventName: 'canvasModified', actionName: 'cursorDrag' @@ -59470,7 +58362,6 @@ Wick.Tools.Cursor = class extends Wick.Tool { } } } - _updateHitResult(e) { var newHitResult = this.paper.project.hitTest(e.point, { fill: true, @@ -59483,44 +58374,39 @@ Wick.Tools.Cursor = class extends Wick.Tool { } }); if (!newHitResult) newHitResult = new this.paper.HitResult(); - if (newHitResult.item && !newHitResult.item.data.isSelectionBoxGUI) { // You can't select children of compound paths, you can only select the whole thing. if (newHitResult.item.parent.className === 'CompoundPath') { newHitResult.item = newHitResult.item.parent; - } // You can't select individual children in a group, you can only select the whole thing. - + } + // You can't select individual children in a group, you can only select the whole thing. if (newHitResult.item.parent.parent) { newHitResult.type = 'fill'; - while (newHitResult.item.parent.parent) { newHitResult.item = newHitResult.item.parent; } - } // this.paper.js has two names for strokes+curves, we don't need that extra info - + } + // this.paper.js has two names for strokes+curves, we don't need that extra info if (newHitResult.type === 'stroke') { newHitResult.type = 'curve'; - } // Mousing over rasters acts the same as mousing over fills. - + } + // Mousing over rasters acts the same as mousing over fills. if (newHitResult.type === 'pixel') { newHitResult.type = 'fill'; } + ; - ; // Disable curve and segment selection. (this was moved to the PathCursor) - + // Disable curve and segment selection. (this was moved to the PathCursor) if (newHitResult.type === 'segment' || newHitResult.type === 'curve') { newHitResult.type = 'fill'; } - ; } - return newHitResult; } - _getCursor() { if (!this.hitResult.item) { return this.CURSOR_DEFAULT; @@ -59528,9 +58414,13 @@ Wick.Tools.Cursor = class extends Wick.Tool { // Don't show any custom cursor if the mouse is over the border, the border does nothing if (this.hitResult.item.name === 'border') { return this.CURSOR_DEFAULT; - } // Calculate the angle in which the scale handle scales the selection. + } + + // Calculate the angle in which the scale handle scales the selection. // Use that angle to determine the cursor graphic to use. + // Here is a handy diagram showing the cursors that correspond to the angles: + // 315 0 45 // o-----o-----o // | | @@ -59541,7 +58431,6 @@ Wick.Tools.Cursor = class extends Wick.Tool { // o-----o-----o // 225 180 135 - var baseAngle = { topCenter: 0, topRight: 45, @@ -59552,17 +58441,17 @@ Wick.Tools.Cursor = class extends Wick.Tool { leftCenter: 270, topLeft: 315 }[this.hitResult.item.data.handleEdge]; - var angle = baseAngle + this._widget.rotation; // It makes angle math easier if we dont allow angles >360 or <0 degrees: - + var angle = baseAngle + this._widget.rotation; + // It makes angle math easier if we dont allow angles >360 or <0 degrees: if (angle < 0) angle += 360; - if (angle > 360) angle -= 360; // Round the angle to the nearest 45 degree interval. + if (angle > 360) angle -= 360; + // Round the angle to the nearest 45 degree interval. var angleRoundedToNearest45 = Math.round(angle / 45) * 45; angleRoundedToNearest45 = Math.round(angleRoundedToNearest45); // just incase of float weirdness - angleRoundedToNearest45 = '' + angleRoundedToNearest45; // convert to string - // Now we know which of eight directions the handle is pointing, so we choose the correct cursor + // Now we know which of eight directions the handle is pointing, so we choose the correct cursor if (this.hitResult.item.data.handleType === 'scale') { var cursorGraphicFromAngle = { '0': this.CURSOR_SCALE_VERTICAL, @@ -59596,66 +58485,50 @@ Wick.Tools.Cursor = class extends Wick.Tool { } } } - _setCursor(cursor) { this.currentCursorIcon = cursor; } - get _selection() { return this.project.selection; } - get _widget() { return this._selection.view.widget; } - _clearSelection() { this._selection.clear(); } - _selectItem(item) { var object = this._wickObjectFromPaperItem(item); - this._selection.select(object); } + /** * Select multiple items simultaneously. * @param {object[]} items paper items */ - - _selectItems(items) { let objects = []; items.forEach(item => { objects.push(this._wickObjectFromPaperItem(item)); }); - this._selection.selectMultipleObjects(objects); } - _deselectItem(item) { var object = this._wickObjectFromPaperItem(item); - this._selection.deselect(object); } - _isItemSelected(item) { var object = this._wickObjectFromPaperItem(item); - return object.isSelected; } - _wickObjectFromPaperItem(item) { var uuid = item.data.wickUUID; - if (!uuid) { console.error('WARNING: _wickObjectFromPaperItem: item had no wick UUID. did you try to select something that wasnt created by a wick view? is the view up-to-date?'); console.log(item); } - return Wick.ObjectCache.getObjectByUUID(uuid); } - }; /* * Copyright 2020 WICKLETS LLC @@ -59675,6 +58548,7 @@ Wick.Tools.Cursor = class extends Wick.Tool { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.Tools.Ellipse = class extends Wick.Tool { /** * Creates an instance of the ellipse tool. @@ -59686,49 +58560,42 @@ Wick.Tools.Ellipse = class extends Wick.Tool { this.topLeft = null; this.bottomRight = null; } - get doubleClickEnabled() { return false; } + /** * A crosshair cursor. * @type {string} */ - - get cursor() { return 'crosshair'; } - get isDrawingTool() { return true; } - onActivate(e) {} - onDeactivate(e) { if (this.path) { this.path.remove(); this.path = null; } } - onMouseDown(e) { this.topLeft = e.point; this.bottomRight = e.point; } - onMouseDrag(e) { if (this.path) this.path.remove(); - this.bottomRight = e.point; // Lock width and height if shift is held down + this.bottomRight = e.point; + // Lock width and height if shift is held down if (e.modifiers.shift) { var d = this.bottomRight.subtract(this.topLeft); var max = Math.max(Math.abs(d.x), Math.abs(d.y)); this.bottomRight.x = this.topLeft.x + max * (d.x < 0 ? -1 : 1); this.bottomRight.y = this.topLeft.y + max * (d.y < 0 ? -1 : 1); } - var bounds = new this.paper.Rectangle(new this.paper.Point(this.topLeft.x, this.topLeft.y), new this.paper.Point(this.bottomRight.x, this.bottomRight.y)); this.path = new this.paper.Path.Ellipse(bounds); this.paper.project.activeLayer.addChild(this.path); @@ -59737,7 +58604,6 @@ Wick.Tools.Ellipse = class extends Wick.Tool { this.path.strokeWidth = this.getSetting('strokeWidth'); this.path.strokeCap = 'round'; } - onMouseUp(e) { if (!this.path) return; this.path.remove(); @@ -59748,7 +58614,6 @@ Wick.Tools.Ellipse = class extends Wick.Tool { actionName: 'ellipse' }); } - }; /* * Copyright 2020 WICKLETS LLC @@ -59768,6 +58633,7 @@ Wick.Tools.Ellipse = class extends Wick.Tool { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.Tools.Eraser = class extends Wick.Tool { /** * @@ -59779,46 +58645,38 @@ Wick.Tools.Eraser = class extends Wick.Tool { this.cursorSize = null; this.cachedCursor = null; } - get doubleClickEnabled() { return false; } + /** * * @type {string} */ - - get cursor() { return this.cachedCursor || 'crosshair'; } - get isDrawingTool() { return true; } - onActivate(e) { this.cursorSize = null; } - onDeactivate(e) { if (this.path) { this.path.remove(); this.path = null; } } - onMouseMove(e) { // Don't render cursor after every mouse move, cache and only render when size changes var cursorNeedsRegen = this.getSetting('eraserSize') !== this.cursorSize; - if (cursorNeedsRegen) { this.cachedCursor = this.createDynamicCursor('#ffffff', this.getSetting('eraserSize') + 1); this.cursorSize = this.getSetting('eraserSize'); this.setCursor(this.cachedCursor); } } - onMouseDown(e) { if (!this.path) { this.path = new this.paper.Path({ @@ -59826,20 +58684,18 @@ Wick.Tools.Eraser = class extends Wick.Tool { strokeCap: 'round', strokeWidth: (this.getSetting('eraserSize') + 1) / this.paper.view.zoom }); - } // Add two points so we always at least have a dot. - + } + // Add two points so we always at least have a dot. this.path.add(e.point); this.path.add(e.point); } - onMouseDrag(e) { if (e.point) { this.path.add(e.point); this.path.smooth(); } } - onMouseUp(e) { if (!this.path) return; var potraceResolution = 0.7; @@ -59856,7 +58712,6 @@ Wick.Tools.Eraser = class extends Wick.Tool { resolution: potraceResolution * this.paper.view.zoom }); } - }; /* * Copyright 2020 WICKLETS LLC @@ -59876,6 +58731,7 @@ Wick.Tools.Eraser = class extends Wick.Tool { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.Tools.Eyedropper = class extends Wick.Tool { /** * @@ -59887,26 +58743,21 @@ Wick.Tools.Eyedropper = class extends Wick.Tool { this.hoverColor = '#ffffff'; this.colorPreview = null; } - get doubleClickEnabled() { return false; } + /** * * @type {string} */ - - get cursor() { return 'url(cursors/eyedropper.png) 32 32, auto'; } - onActivate(e) {} - onDeactivate(e) { this._destroyColorPreview(); } - onMouseMove(e) { super.onMouseMove(e); var canvas = this.paper.view._element; @@ -59917,13 +58768,10 @@ Wick.Tools.Eyedropper = class extends Wick.Tool { var colorData = ctx.getImageData(pointPx.x, pointPx.y, 1, 1).data; var colorCSS = 'rgb(' + colorData[0] + ',' + colorData[1] + ',' + colorData[2] + ')'; this.hoverColor = colorCSS; - this._createColorPreview(e.point); } - onMouseDown(e) { this._destroyColorPreview(); - this.fireEvent({ eventName: 'eyedropperPickedColor', e: { @@ -59931,16 +58779,12 @@ Wick.Tools.Eyedropper = class extends Wick.Tool { } }); } - onMouseDrag(e) {} - onMouseUp(e) { this._createColorPreview(e.point); } - _createColorPreview(point) { this._destroyColorPreview(); - var offset = 10 / this.paper.view.zoom; var center = point.add(new paper.Point(offset + 0.5, offset + 0.5)); var radius = 10 / paper.view.zoom; @@ -59954,14 +58798,12 @@ Wick.Tools.Eyedropper = class extends Wick.Tool { strokeWidth: 1.0 / this.paper.view.zoom })); } - _destroyColorPreview() { if (this.colorPreview) { this.colorPreview.remove(); this.colorPreview = null; } } - }; /* * Copyright 2020 WICKLETS LLC @@ -59981,6 +58823,7 @@ Wick.Tools.Eyedropper = class extends Wick.Tool { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.Tools.FillBucket = class extends Wick.Tool { /** * @@ -59989,28 +58832,22 @@ Wick.Tools.FillBucket = class extends Wick.Tool { super(); this.name = 'fillbucket'; } - get doubleClickEnabled() { return false; } + /** * * @type {string} */ - - get cursor() { return 'url(cursors/fillbucket.png) 32 32, auto'; } - get isDrawingTool() { return true; } - onActivate(e) {} - onDeactivate(e) {} - onMouseDown(e) { setTimeout(() => { this.setCursor('wait'); @@ -60027,19 +58864,16 @@ Wick.Tools.FillBucket = class extends Wick.Tool { }), onFinish: path => { this.setCursor('default'); - if (path) { path.fillColor = this.getSetting('fillColor').rgba; path.name = null; this.addPathToProject(); - if (e.item) { path.insertAbove(e.item); } else { this.paper.project.activeLayer.addChild(path); this.paper.OrderingUtils.sendToBack([path]); } - this.fireEvent({ eventName: 'canvasModified', actionName: 'fillbucket' @@ -60053,11 +58887,8 @@ Wick.Tools.FillBucket = class extends Wick.Tool { }); }, 50); } - onMouseDrag(e) {} - onMouseUp(e) {} - }; /* * Copyright 2020 WICKLETS LLC @@ -60077,6 +58908,7 @@ Wick.Tools.FillBucket = class extends Wick.Tool { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.Tools.Interact = class extends Wick.Tool { /** * Creates an Interact tool. @@ -60090,71 +58922,55 @@ Wick.Tools.Interact = class extends Wick.Tool { this._mousePosition = new paper.Point(0, 0); this._mouseTargets = []; } - onActivate(e) {} - onDeactivate(e) {} - onMouseMove(e) { this._mousePosition = e.point; } - onMouseDrag(e) { this._mousePosition = e.point; } - onMouseDown(e) { this._mousePosition = e.point; this._mouseIsDown = true; } - onMouseUp(e) { this._mousePosition = e.point; this._mouseIsDown = false; } - onKeyDown(e) { this._lastKeyDown = e.key; - if (this._keysDown.indexOf(e.key) === -1) { this._keysDown.push(e.key); } } - onKeyUp(e) { this._keysDown = this._keysDown.filter(key => { return key !== e.key; }); } - get mousePosition() { return this._mousePosition; } - get mouseIsDown() { return this._mouseIsDown; } - get keysDown() { return this._keysDown; } - get lastKeyDown() { return this._lastKeyDown; } - get mouseTargets() { return this._mouseTargets; } - get doubleClickEnabled() { return false; } + /** * Use the current position of the mouse to determine which object(s) are under the mouse */ - - determineMouseTargets() { var targets = []; var hitResult = this.paper.project.hitTest(this.mousePosition, { @@ -60162,14 +58978,13 @@ Wick.Tools.Interact = class extends Wick.Tool { stroke: true, curves: true, segments: true - }); // Check for clips under the mouse. + }); + // Check for clips under the mouse. if (hitResult) { var uuid = hitResult.item.data.wickUUID; - if (uuid) { var path = Wick.ObjectCache.getObjectByUUID(uuid); - if (path && !path.parentClip.isRoot) { var clip = path.parentClip; var lineageWithoutRoot = clip.lineage; @@ -60182,9 +58997,9 @@ Wick.Tools.Interact = class extends Wick.Tool { targets = [this.project.activeFrame]; } else { targets = []; - } // Update cursor - + } + // Update cursor if (this.project.hideCursor) { this.setCursor('none'); } else { @@ -60193,10 +59008,8 @@ Wick.Tools.Interact = class extends Wick.Tool { clip && this.setCursor(clip.cursor); } } - this._mouseTargets = targets; } - }; /* * Copyright 2020 WICKLETS LLC @@ -60216,6 +59029,7 @@ Wick.Tools.Interact = class extends Wick.Tool { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.Tools.Line = class extends Wick.Tool { /** * @@ -60229,36 +59043,29 @@ Wick.Tools.Line = class extends Wick.Tool { this.startPoint; this.endPoint; } - get doubleClickEnabled() { return false; } + /** * * @type {string} */ - - get cursor() { return 'crosshair'; } - get isDrawingTool() { return true; } - onActivate(e) { this.path.remove(); } - onDeactivate(e) { this.path.remove(); } - onMouseDown(e) { this.startPoint = e.point; } - onMouseDrag(e) { this.path.remove(); this.endPoint = e.point; @@ -60267,7 +59074,6 @@ Wick.Tools.Line = class extends Wick.Tool { this.path.strokeColor = this.getSetting('strokeColor').rgba; this.path.strokeWidth = this.getSetting('strokeWidth'); } - onMouseUp(e) { this.path.remove(); this.addPathToProject(this.path); @@ -60276,7 +59082,6 @@ Wick.Tools.Line = class extends Wick.Tool { actionName: 'line' }); } - }; /* * Copyright 2020 WICKLETS LLC @@ -60296,6 +59101,7 @@ Wick.Tools.Line = class extends Wick.Tool { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.Tools.None = class extends Wick.Tool { /** * Creates a none tool. @@ -60304,23 +59110,18 @@ Wick.Tools.None = class extends Wick.Tool { super(); this.name = 'none'; } + /** * The "no-sign" cursor. * @type {string} */ - - get cursor() { return 'not-allowed'; } - onActivate(e) {} - onDeactivate(e) {} - onMouseDown(e) { var message = ''; - if (!this.project.activeFrame) { message = 'CLICK_NOT_ALLOWED_NO_FRAME'; } else if (this.project.activeLayer.locked) { @@ -60330,14 +59131,10 @@ Wick.Tools.None = class extends Wick.Tool { } else { return; } - this.project.errorOccured(message); } - onMouseDrag(e) {} - onMouseUp(e) {} - }; /* * Copyright 2020 WICKLETS LLC @@ -60357,6 +59154,7 @@ Wick.Tools.None = class extends Wick.Tool { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.Tools.Pan = class extends Wick.Tool { /** * @@ -60365,37 +59163,29 @@ Wick.Tools.Pan = class extends Wick.Tool { super(); this.name = 'pan'; } - get doubleClickEnabled() { return false; } + /** * * @type {string} */ - - get cursor() { return 'move'; } - onActivate(e) {} - onDeactivate(e) {} - onMouseDown(e) {} - onMouseDrag(e) { var d = e.downPoint.subtract(e.point); this.paper.view.center = this.paper.view.center.add(d); } - onMouseUp(e) { this.fireEvent({ eventName: 'canvasViewTransformed' }); } - }; /* * Copyright 2020 WICKLETS LLC @@ -60415,6 +59205,7 @@ Wick.Tools.Pan = class extends Wick.Tool { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.Tools.PathCursor = class extends Wick.Tool { constructor() { super(); @@ -60438,31 +59229,29 @@ Wick.Tools.PathCursor = class extends Wick.Tool { this.detailedEditing = null; this.currentCursorIcon = ''; } - get doubleClickEnabled() { return true; } - get cursor() { return 'url("' + this.currentCursorIcon + '") 32 32, auto'; } - onActivate(e) {} - onDeactivate(e) { this._leaveDetailedEditing(); } - onMouseMove(e) { - super.onMouseMove(e); // Remove the hover preview, a new one will be generated if needed - - this.hoverPreview.remove(); // Find the thing that is currently under the cursor. + super.onMouseMove(e); - this.hitResult = this._updateHitResult(e); // Update the image being used for the cursor + // Remove the hover preview, a new one will be generated if needed + this.hoverPreview.remove(); - this._setCursor(this._getCursor()); // Regen hover preview + // Find the thing that is currently under the cursor. + this.hitResult = this._updateHitResult(e); + // Update the image being used for the cursor + this._setCursor(this._getCursor()); + // Regen hover preview if (this.hitResult.type === 'segment' && !this.hitResult.item.data.isSelectionBoxGUI) { // Hovering over a segment, draw a circle where the segment is this.hoverPreview = new this.paper.Path.Circle(this.hitResult.segment.point, this.HOVER_PREVIEW_SEGMENT_RADIUS / this.paper.view.zoom); @@ -60479,20 +59268,16 @@ Wick.Tools.PathCursor = class extends Wick.Tool { this.hoverPreview.segments[0].handleOut = this.hitResult.location.curve.handle1; this.hoverPreview.segments[1].handleIn = this.hitResult.location.curve.handle2; } - this.hoverPreview.data.wickType = 'gui'; } - onMouseDown(e) { super.onMouseDown(e); if (!e.modifiers) e.modifiers = {}; this.hitResult = this._updateHitResult(e); - if (this.detailedEditing !== null && !(this.hitResult.item || this.hitResult.type && this.hitResult.type.startsWith('handle'))) { // Clicked neither on the currently edited path nor on a handle. this._leaveDetailedEditing(); } - if (this.hitResult.item && this.hitResult.type === 'curve') { // Clicked a curve, start dragging it this.draggingCurve = this.hitResult.location.curve; @@ -60502,10 +59287,8 @@ Wick.Tools.PathCursor = class extends Wick.Tool { } } } - onDoubleClick(e) { this.hitResult = this._updateHitResult(e); - if (this.detailedEditing == null) { // If detailed editing is off, turn it on for this path. this.detailedEditing = this.hitResult.item; @@ -60518,12 +59301,10 @@ Wick.Tools.PathCursor = class extends Wick.Tool { var location = this.hitResult.location; var path = this.hitResult.item; var addedPoint = path.insert(location.index + 1, e.point); - if (!e.modifiers.shift) { addedPoint.smooth(); var handleInMag = Math.sqrt(addedPoint.handleIn.x * addedPoint.handleIn.x + addedPoint.handleIn.y + addedPoint.handleIn.y); var handleOutMag = Math.sqrt(addedPoint.handleOut.x * addedPoint.handleOut.x + addedPoint.handleOut.y + addedPoint.handleOut.y); - if (handleInMag > handleOutMag) { var avgMag = handleOutMag; addedPoint.handleIn.x = -addedPoint.handleOut.x * 1.5; @@ -60538,7 +59319,6 @@ Wick.Tools.PathCursor = class extends Wick.Tool { addedPoint.handleIn.y *= 1.5; } } - if (this.detailedEditing !== null) { path.setFullySelected(true); } @@ -60547,7 +59327,6 @@ Wick.Tools.PathCursor = class extends Wick.Tool { var hiy = this.hitResult.segment.handleIn.y; var hox = this.hitResult.segment.handleOut.x; var hoy = this.hitResult.segment.handleOut.y; - if (hix === 0 && hiy === 0 && hix === 0 && hiy === 0) { this.hitResult.segment.smooth(); } else { @@ -60558,10 +59337,8 @@ Wick.Tools.PathCursor = class extends Wick.Tool { } } } - onMouseDrag(e) { if (!e.modifiers) e.modifiers = {}; - if (this.hitResult.item && this.hitResult.type === 'segment') { // We're dragging an individual point, so move the point. this.hitResult.segment.point = this.hitResult.segment.point.add(e.delta); @@ -60572,30 +59349,26 @@ Wick.Tools.PathCursor = class extends Wick.Tool { var segment2 = this.draggingCurve.segment2; var handleIn = segment1.handleOut; var handleOut = segment2.handleIn; - if (handleIn.x === 0 && handleIn.y === 0) { handleIn.x = (segment2.point.x - segment1.point.x) / 4; handleIn.y = (segment2.point.y - segment1.point.y) / 4; } - if (handleOut.x === 0 && handleOut.y === 0) { handleOut.x = (segment1.point.x - segment2.point.x) / 4; handleOut.y = (segment1.point.y - segment2.point.y) / 4; } - handleIn.x += e.delta.x; handleIn.y += e.delta.y; handleOut.x += e.delta.x; - handleOut.y += e.delta.y; // Update the hover preview to match the curve we just changed + handleOut.y += e.delta.y; + // Update the hover preview to match the curve we just changed this.hoverPreview.segments[0].handleOut = this.draggingCurve.handle1; this.hoverPreview.segments[1].handleIn = this.draggingCurve.handle2; } - if (this.hitResult.type && this.hitResult.type.startsWith('handle')) { var otherHandle; var handle; - if (this.hitResult.type === 'handle-in') { handle = this.hitResult.segment.handleIn; otherHandle = this.hitResult.segment.handleOut; @@ -60603,17 +59376,14 @@ Wick.Tools.PathCursor = class extends Wick.Tool { handle = this.hitResult.segment.handleOut; otherHandle = this.hitResult.segment.handleIn; } - handle.x += e.delta.x; handle.y += e.delta.y; - if (!e.modifiers.shift) { otherHandle.x -= e.delta.x; otherHandle.y -= e.delta.y; } } } - onMouseUp(e) { if (this.hitResult.type === 'segment' || this.hitResult.type === 'curve') { this.fireEvent({ @@ -60622,7 +59392,6 @@ Wick.Tools.PathCursor = class extends Wick.Tool { }); } } - onKeyDown(e) { if (this.detailedEditing !== null && e.key == "<") { var wick = Wick.ObjectCache.getObjectByUUID(this._getWickUUID(this.detailedEditing)); @@ -60631,7 +59400,6 @@ Wick.Tools.PathCursor = class extends Wick.Tool { this.fireEvent('canvasModified'); } } - _updateHitResult(e) { var newHitResult = this.paper.project.hitTest(e.point, { fill: true, @@ -60645,48 +59413,42 @@ Wick.Tools.PathCursor = class extends Wick.Tool { } }); if (!newHitResult) newHitResult = new this.paper.HitResult(); - if (this.detailedEditing !== null) { if (this._getWickUUID(newHitResult.item) !== this._getWickUUID(this.detailedEditing)) { // Hits an item, but not the one currently in detail edit - handle as a click with no hit. return new this.paper.HitResult(); } - if (newHitResult.item && newHitResult.type.startsWith('handle')) { // If this a click on a handle, do not apply hit type prediction below. return newHitResult; } } - if (newHitResult.item && !newHitResult.item.data.isSelectionBoxGUI) { // You can't select children of compound paths, you can only select the whole thing. if (newHitResult.item.parent.className === 'CompoundPath') { newHitResult.item = newHitResult.item.parent; - } // You can't select individual children in a group, you can only select the whole thing. - + } + // You can't select individual children in a group, you can only select the whole thing. if (newHitResult.item.parent.parent) { newHitResult.type = 'fill'; - while (newHitResult.item.parent.parent) { newHitResult.item = newHitResult.item.parent; } - } // this.paper.js has two names for strokes+curves, we don't need that extra info - + } + // this.paper.js has two names for strokes+curves, we don't need that extra info if (newHitResult.type === 'stroke') { newHitResult.type = 'curve'; - } // Mousing over rasters acts the same as mousing over fills. - + } + // Mousing over rasters acts the same as mousing over fills. if (newHitResult.type === 'pixel') { newHitResult.type = 'fill'; } } - return newHitResult; } - _getCursor() { if (!this.hitResult.item) { return this.CURSOR_DEFAULT; @@ -60696,11 +59458,9 @@ Wick.Tools.PathCursor = class extends Wick.Tool { return this.CURSOR_SEGMENT; } } - _setCursor(cursor) { this.currentCursorIcon = cursor; } - _leaveDetailedEditing() { if (this.detailedEditing !== null) { this.paper.project.deselectAll(); @@ -60713,7 +59473,6 @@ Wick.Tools.PathCursor = class extends Wick.Tool { this.fireEvent('canvasModified'); } } - _getWickUUID(item) { if (item) { return item.data.wickUUID; @@ -60721,7 +59480,6 @@ Wick.Tools.PathCursor = class extends Wick.Tool { return undefined; } } - }; /* * Copyright 2020 WICKLETS LLC @@ -60741,46 +59499,39 @@ Wick.Tools.PathCursor = class extends Wick.Tool { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.Tools.Pencil = class extends Wick.Tool { static get MIN_ADD_POINT_MOVEMENT() { return 2; } + /** * Creates a pencil tool. */ - - constructor() { super(); this.name = 'pencil'; this.path = null; this._movement = new paper.Point(); } - get doubleClickEnabled() { return false; } + /** * The pencil cursor. * @type {string} */ - - get cursor() { return 'url(cursors/pencil.png) 32 32, auto'; } - get isDrawingTool() { return true; } - onActivate(e) {} - onDeactivate(e) {} - onMouseDown(e) { this._movement = new paper.Point(); - if (!this.path) { this.path = new this.paper.Path({ strokeColor: this.getSetting('strokeColor').rgba, @@ -60788,21 +59539,17 @@ Wick.Tools.Pencil = class extends Wick.Tool { strokeCap: 'round' }); } - this.path.add(e.point); } - onMouseDrag(e) { if (!this.path) return; this._movement = this._movement.add(e.delta); - if (this._movement.length > Wick.Tools.Pencil.MIN_ADD_POINT_MOVEMENT / this.paper.view.zoom) { this._movement = new paper.Point(); this.path.add(e.point); this.path.smooth(); } } - onMouseUp(e) { if (!this.path) return; this.path.add(e.point); @@ -60815,7 +59562,6 @@ Wick.Tools.Pencil = class extends Wick.Tool { actionName: 'pencil' }); } - }; /* * Copyright 2020 WICKLETS LLC @@ -60835,6 +59581,7 @@ Wick.Tools.Pencil = class extends Wick.Tool { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.Tools.Rectangle = class extends Wick.Tool { /** * @@ -60846,63 +59593,53 @@ Wick.Tools.Rectangle = class extends Wick.Tool { this.topLeft = null; this.bottomRight = null; } - get doubleClickEnabled() { return false; } + /** * * @type {string} */ - - get cursor() { return 'crosshair'; } - get isDrawingTool() { return true; } - onActivate(e) {} - onDeactivate(e) { if (this.path) { this.path.remove(); this.path = null; } } - onMouseDown(e) { this.topLeft = e.point; this.bottomRight = e.point; } - onMouseDrag(e) { if (this.path) this.path.remove(); - this.bottomRight = e.point; // Lock width and height if shift is held down + this.bottomRight = e.point; + // Lock width and height if shift is held down if (e.modifiers.shift) { var d = this.bottomRight.subtract(this.topLeft); var max = Math.max(Math.abs(d.x), Math.abs(d.y)); this.bottomRight.x = this.topLeft.x + max * (d.x < 0 ? -1 : 1); this.bottomRight.y = this.topLeft.y + max * (d.y < 0 ? -1 : 1); } - var bounds = new this.paper.Rectangle(new paper.Point(this.topLeft.x, this.topLeft.y), new paper.Point(this.bottomRight.x, this.bottomRight.y)); - if (this.getSetting('cornerRadius') !== 0) { this.path = new this.paper.Path.Rectangle(bounds, this.getSetting('cornerRadius')); } else { this.path = new this.paper.Path.Rectangle(bounds); } - this.path.fillColor = this.getSetting('fillColor').rgba; this.path.strokeColor = this.getSetting('strokeColor').rgba; this.path.strokeWidth = this.getSetting('strokeWidth'); this.path.strokeCap = 'round'; } - onMouseUp(e) { if (!this.path) return; this.path.remove(); @@ -60913,7 +59650,6 @@ Wick.Tools.Rectangle = class extends Wick.Tool { actionName: 'rectangle' }); } - }; /* * Copyright 2020 WICKLETS LLC @@ -60933,6 +59669,7 @@ Wick.Tools.Rectangle = class extends Wick.Tool { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.Tools.Text = class extends Wick.Tool { /** * @@ -60943,37 +59680,29 @@ Wick.Tools.Text = class extends Wick.Tool { this.hoveredOverText = null; this.editingText = null; } - get doubleClickEnabled() { return false; } + /** * * @type {string} */ - - get cursor() { return 'text'; } - get isDrawingTool() { return true; } - onActivate(e) {} - onDeactivate(e) { if (this.editingText) { this.finishEditingText(); } - this.hoveredOverText = null; } - onMouseMove(e) { super.onMouseMove(e); - if (e.item && e.item.className === 'PointText' && !e.item.parent.parent) { this.hoveredOverText = e.item; this.setCursor('text'); @@ -60982,7 +59711,6 @@ Wick.Tools.Text = class extends Wick.Tool { this.setCursor('url(cursors/text.png) 32 32, auto'); } } - onMouseDown(e) { if (this.editingText) { this.finishEditingText(); @@ -61003,37 +59731,33 @@ Wick.Tools.Text = class extends Wick.Tool { this.project.activeFrame.addPath(wickText); this.project.view.render(); this.editingText = wickText.view.item; - this.editingText.edit(this.project.view.paper); //this.fireEvent('canvasModified'); + this.editingText.edit(this.project.view.paper); + + //this.fireEvent('canvasModified'); } } onMouseDrag(e) {} - onMouseUp(e) {} - reset() { this.finishEditingText(); } + /** * Stop editing the current text and apply changes. */ - - finishEditingText() { if (!this.editingText) return; this.editingText.finishEditing(); - if (this.editingText.content === '') { this.editingText.remove(); } - this.editingText = null; this.fireEvent({ eventName: 'canvasModified', actionName: 'text' }); } - }; /* * Copyright 2020 WICKLETS LLC @@ -61053,6 +59777,7 @@ Wick.Tools.Text = class extends Wick.Tool { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + Wick.Tools.Zoom = class extends Wick.Tool { /** * @@ -61065,33 +59790,26 @@ Wick.Tools.Zoom = class extends Wick.Tool { this.MIN_ZOOMBOX_SIZE = 20; this.zoomBox = null; } - get doubleClickEnabled() { return false; } + /** * * @type {string} */ - - get cursor() { return 'zoom-in'; } - onActivate(e) {} - onDeactivate(e) { this.deleteZoomBox(); } - onMouseDown(e) {} - onMouseDrag(e) { this.deleteZoomBox(); this.createZoomBox(e); } - onMouseUp(e) { if (this.zoomBox && this.zoomBoxIsValidSize()) { var bounds = this.zoomBox.bounds; @@ -61102,13 +59820,11 @@ Wick.Tools.Zoom = class extends Wick.Tool { var zoomAmount = e.modifiers.alt ? this.ZOOM_OUT_AMOUNT : this.ZOOM_IN_AMOUNT; this.paper.view.scale(zoomAmount, e.point); } - this.deleteZoomBox(); this.fireEvent({ eventName: 'canvasViewTransformed' }); } - createZoomBox(e) { var bounds = new this.paper.Rectangle(e.downPoint, e.point); bounds.x += 0.5; @@ -61117,18 +59833,15 @@ Wick.Tools.Zoom = class extends Wick.Tool { this.zoomBox.strokeColor = 'black'; this.zoomBox.strokeWidth = 1.0 / this.paper.view.zoom; } - deleteZoomBox() { if (this.zoomBox) { this.zoomBox.remove(); this.zoomBox = null; } } - zoomBoxIsValidSize() { return this.zoomBox.bounds.width > this.MIN_ZOOMBOX_SIZE && this.zoomBox.bounds.height > this.MIN_ZOOMBOX_SIZE; } - }; /* * Copyright 2020 WICKLETS LLC @@ -61156,6 +59869,7 @@ Wick.Tools.Zoom = class extends Wick.Tool { by zrispo (github.com/zrispo) (zach@wickeditor.com) */ + (function () { // Splits a CompoundPath with multiple CW children into individual pieces function splitCompoundPath(compoundPath) { @@ -61173,8 +59887,9 @@ Wick.Tools.Zoom = class extends Wick.Tool { part.insertAbove(compoundPath); parts.push(part); } - }); // Find hole ownership for each 'part' + }); + // Find hole ownership for each 'part' var resolvedHoles = []; parts.forEach(function (part) { var cmp; @@ -61189,19 +59904,18 @@ Wick.Tools.Zoom = class extends Wick.Tool { insert: false })); } - cmp.addChild(hole); resolvedHoles.push(hole); } - if (cmp) { cmp.fillColor = compoundPath.fillColor; cmp.insertAbove(part); part.remove(); } }); - }); // If any holes could not find a path to be a part of, turn them into their own paths + }); + // If any holes could not find a path to be a part of, turn them into their own paths holes.filter(hole => { return resolvedHoles.indexOf(hole) === -1; }).forEach(hole => { @@ -61210,7 +59924,6 @@ Wick.Tools.Zoom = class extends Wick.Tool { }); compoundPath.remove(); } - function eraseFill(path, eraserPath) { if (path.closePath) path.closePath(); var res = path.subtract(eraserPath, { @@ -61218,7 +59931,6 @@ Wick.Tools.Zoom = class extends Wick.Tool { trace: true }); res.fillColor = path.fillColor; - if (res.children) { res.insertAbove(path); res.data = {}; @@ -61229,19 +59941,15 @@ Wick.Tools.Zoom = class extends Wick.Tool { res.data = {}; res.insertAbove(path); } - path.remove(); } - path.remove(); } - function eraseStroke(path, eraserPath) { var res = path.subtract(eraserPath, { insert: false, trace: false }); - if (res.children) { // Since the path is only strokes, it's trivial to split it into individual paths var children = []; @@ -61258,10 +59966,8 @@ Wick.Tools.Zoom = class extends Wick.Tool { res.remove(); if (res.segments.length > 0) res.insertAbove(path); } - path.remove(); } - function splitPath(path) { var fill = path.clone({ insert: false @@ -61282,7 +59988,6 @@ Wick.Tools.Zoom = class extends Wick.Tool { stroke: stroke }; } - function eraseWithPath(eraserPath) { var erasables = this.children.filter(path => { return path instanceof paper.Path || path instanceof paper.CompoundPath; @@ -61302,7 +60007,6 @@ Wick.Tools.Zoom = class extends Wick.Tool { } }); } - paper.Layer.inject({ erase: eraseWithPath }); @@ -61334,6 +60038,7 @@ Wick.Tools.Zoom = class extends Wick.Tool { Adapted from the FillBucket tool from old Wick by zrispo (github.com/zrispo) (zach@wickeditor.com) */ + (function () { var VERBOSE = false; var PREVIEW_IMAGE = false; @@ -61348,12 +60053,10 @@ Wick.Tools.Zoom = class extends Wick.Tool { var floodFillY; var bgColor; var gapFillAmount; - function previewImage(image) { var win = window.open('', 'Title', 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=yes, resizable=yes, width=' + image.width + ', height=' + image.height + ', top=100, left=100'); win.document.body.innerHTML = '
'; } - function rasterizePaths(callback) { var layerGroup = new paper.Group({ insert: false @@ -61361,29 +60064,26 @@ Wick.Tools.Zoom = class extends Wick.Tool { layers.reverse().forEach(layer => { layer.children.forEach(function (child) { if (child._class !== 'Path' && child._class !== 'CompoundPath') return; - for (var i = 0; i < N_RASTER_CLONE; i++) { var clone = child.clone({ insert: false - }); //experiment: bump out all strokes a bit by expanding their stroke widths + }); + //experiment: bump out all strokes a bit by expanding their stroke widths if (!clone.strokeColor && clone.fillColor) { clone.strokeColor = clone.fillColor; clone.strokeWidth = gapFillAmount / RASTER_BASE_RESOLUTION; } else if (clone.strokeWidth) { clone.strokeWidth += gapFillAmount / RASTER_BASE_RESOLUTION; } - layerGroup.addChild(clone); } }); }); - if (layerGroup.children.length === 0) { onError('NO_PATHS'); return; } - var rasterResolution = paper.view.resolution * RASTER_BASE_RESOLUTION / window.devicePixelRatio; var layerPathsRaster = layerGroup.rasterize(rasterResolution, { insert: false @@ -61392,7 +60092,6 @@ Wick.Tools.Zoom = class extends Wick.Tool { var rasterCtx = rasterCanvas.getContext('2d'); var layerPathsImageData = rasterCtx.getImageData(0, 0, layerPathsRaster.width, layerPathsRaster.height); var layerPathsImageDataRaw = layerPathsImageData.data; - for (var i = 0; i < layerPathsImageDataRaw.length; i += 4) { if (layerPathsImageDataRaw[i + 3] === 0) { layerPathsImageDataRaw[i] = bgColor.red; @@ -61401,7 +60100,6 @@ Wick.Tools.Zoom = class extends Wick.Tool { layerPathsImageDataRaw[i + 3] = 255; } } - rasterCtx.putImageData(layerPathsImageData, 0, 0); layerPathsImageData = rasterCtx.getImageData(0, 0, layerPathsRaster.width, layerPathsRaster.height); var rasterPosition = layerPathsRaster.bounds.topLeft; @@ -61412,19 +60110,16 @@ Wick.Tools.Zoom = class extends Wick.Tool { var floodFillCanvas = document.createElement('canvas'); floodFillCanvas.width = layerPathsRaster.canvas.width; floodFillCanvas.height = layerPathsRaster.canvas.height; - if (x < 0 || y < 0 || x >= floodFillCanvas.width || y >= floodFillCanvas.height) { onError('OUT_OF_BOUNDS'); return; } - var floodFillCtx = floodFillCanvas.getContext('2d'); floodFillCtx.putImageData(layerPathsImageData, 0, 0); floodFillCtx.fillStyle = "rgba(123,124,125,255)"; floodFillCtx.fillFlood(x, y, FILL_TOLERANCE); var floodFillImageData = floodFillCtx.getImageData(0, 0, floodFillCanvas.width, floodFillCanvas.height); var imageDataRaw = floodFillImageData.data; - for (var i = 0; i < imageDataRaw.length; i += 4) { if (imageDataRaw[i] === 123 && imageDataRaw[i + 1] === 124 && imageDataRaw[i + 2] === 125) { imageDataRaw[i] = 0; @@ -61438,16 +60133,14 @@ Wick.Tools.Zoom = class extends Wick.Tool { imageDataRaw[i + 3] = 0; } } - floodFillCtx.putImageData(floodFillImageData, 0, 0); var floodFillProcessedImage = new Image(); - floodFillProcessedImage.onload = function () { if (PREVIEW_IMAGE) previewImage(floodFillProcessedImage); var svgString = potrace.fromImage(floodFillProcessedImage).toSVG(1); var xmlString = svgString, - parser = new DOMParser(), - doc = parser.parseFromString(xmlString, "text/xml"); + parser = new DOMParser(), + doc = parser.parseFromString(xmlString, "text/xml"); var resultHolePath = paper.project.importSVG(doc, { insert: true }); @@ -61461,7 +60154,6 @@ Wick.Tools.Zoom = class extends Wick.Tool { var holeIsLeaky = false; var w = floodFillProcessedImage.width; var h = floodFillProcessedImage.height; - for (var x = 0; x < floodFillProcessedImage.width; x++) { if (getPixelAt(x, 0, w, h, floodFillImageData.data).r === 0 && getPixelAt(x, 0, w, h, floodFillImageData.data).a === 255) { holeIsLeaky = true; @@ -61469,27 +60161,21 @@ Wick.Tools.Zoom = class extends Wick.Tool { return; } } - expandHole(resultHolePath); callback(resultHolePath); }; - floodFillProcessedImage.src = floodFillCanvas.toDataURL(); } - function expandHole(path) { if (path instanceof paper.Group) { path = path.children[0]; } - var children; - if (path instanceof paper.Path) { children = [path]; } else if (path instanceof paper.CompoundPath) { children = path.children; } - children.forEach(function (hole) { var normals = []; hole.closePath(); @@ -61518,7 +60204,6 @@ Wick.Tools.Zoom = class extends Wick.Tool { y: d.y }); }); - for (var i = 0; i < hole.segments.length; i++) { var segment = hole.segments[i]; var normal = normals[i]; @@ -61526,9 +60211,9 @@ Wick.Tools.Zoom = class extends Wick.Tool { segment.point.y += normal.y * EXPAND_AMT; } }); - } // http://www.felixeve.co.uk/how-to-rotate-a-point-around-an-origin-with-javascript/ - + } + // http://www.felixeve.co.uk/how-to-rotate-a-point-around-an-origin-with-javascript/ function rotate_point(pointX, pointY, originX, originY, angle) { angle = angle * Math.PI / 180.0; return { @@ -61536,7 +60221,6 @@ Wick.Tools.Zoom = class extends Wick.Tool { y: Math.sin(angle) * (pointX - originX) + Math.cos(angle) * (pointY - originY) + originY }; } - function getPixelAt(x, y, width, height, imageData) { if (x < 0 || y < 0 || x >= width || y >= height) return null; var offset = (y * width + x) * 4; @@ -61547,9 +60231,8 @@ Wick.Tools.Zoom = class extends Wick.Tool { a: imageData[offset + 3] }; } - /* Add hole() method to paper */ - + /* Add hole() method to paper */ paper.PaperScope.inject({ hole: function (args) { if (!args) console.error('paper.hole: args is required'); @@ -61587,6 +60270,7 @@ Wick.Tools.Zoom = class extends Wick.Tool { * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + class PaperJSOrderingUtils { /** * Moves the selected items forwards. @@ -61600,11 +60284,10 @@ class PaperJSOrderingUtils { }); }); } + /** * Moves the selected items backwards. */ - - static moveBackwards(items) { PaperJSOrderingUtils._sortItemsByLayer(items).forEach(layerItems => { PaperJSOrderingUtils._sortItemsByZIndex(layerItems).forEach(item => { @@ -61614,11 +60297,10 @@ class PaperJSOrderingUtils { }); }); } + /** * Brings the selected objects to the front. */ - - static bringToFront(items) { PaperJSOrderingUtils._sortItemsByLayer(items).forEach(layerItems => { PaperJSOrderingUtils._sortItemsByZIndex(layerItems).forEach(item => { @@ -61626,11 +60308,10 @@ class PaperJSOrderingUtils { }); }); } + /** * Sends the selected objects to the back. */ - - static sendToBack(items) { PaperJSOrderingUtils._sortItemsByLayer(items).forEach(layerItems => { PaperJSOrderingUtils._sortItemsByZIndex(layerItems).reverse().forEach(item => { @@ -61638,38 +60319,32 @@ class PaperJSOrderingUtils { }); }); } - static _sortItemsByLayer(items) { var layerLists = {}; items.forEach(item => { // Create new list for the item's layer if it doesn't exist var layerID = item.layer.id; - if (!layerLists[layerID]) { layerLists[layerID] = []; - } // Add this item to its corresponding layer list - + } + // Add this item to its corresponding layer list layerLists[layerID].push(item); - }); // Convert id->array object to array of arrays + }); + // Convert id->array object to array of arrays var layerItemsArrays = []; - for (var layerID in layerLists) { layerItemsArrays.push(layerLists[layerID]); } - return layerItemsArrays; } - static _sortItemsByZIndex(items) { return items.sort(function (a, b) { return a.index - b.index; }); } - } - ; paper.PaperScope.inject({ OrderingUtils: PaperJSOrderingUtils @@ -61692,6 +60367,7 @@ paper.PaperScope.inject({ * You should have received a copy of the GNU General Public License * along with Wick Engine. If not, see . */ + class SelectionWidget { /** * Creates a SelectionWidget @@ -61704,145 +60380,124 @@ class SelectionWidget { insert: false }); } + /** * The item containing the widget GUI */ - - get item() { return this._item; } + /** * The layer to add the widget GUI item to. */ - - get layer() { return this._layer; } - set layer(layer) { this._layer = layer; } + /** * The rotation of the selection box GUI. */ - - get boxRotation() { return this._boxRotation; } - set boxRotation(boxRotation) { this._boxRotation = boxRotation; } + /** * The items currently inside the selection widget */ - - get itemsInSelection() { return this._itemsInSelection; } + /** * The point to rotate/scale the widget around. */ - - get pivot() { return this._pivot; } - set pivot(pivot) { this._pivot = pivot; } + /** * The position of the top left corner of the selection box. */ - - get position() { return this._boundingBox.topLeft.rotate(this.rotation, this.pivot); } - set position(position) { var d = position.subtract(this.position); this.translateSelection(d); } + /** * The width of the selection. */ - - get width() { return this._boundingBox.width; } - set width(width) { var d = width / this.width; if (d === 0) d = 0.001; this.scaleSelection(new paper.Point(d, 1.0)); } + /** * The height of the selection. */ - - get height() { return this._boundingBox.height; } - set height(height) { var d = height / this.height; this.scaleSelection(new paper.Point(1.0, d)); } + /** * The rotation of the selection. */ - - get rotation() { return this._boxRotation; } - set rotation(rotation) { var d = rotation - this.rotation; this.rotateSelection(d); } + /** * Flip the selected items horizontally. */ - - flipHorizontally() { this.scaleSelection(new paper.Point(-1.0, 1.0)); } + /** * Flip the selected items vertically. */ - - flipVertically() { this.scaleSelection(new paper.Point(1.0, -1.0)); } + /** * The bounding box of the widget. */ - - get boundingBox() { return this._boundingBox; } + /** * The current transformation being done to the selection widget. * @type {string} */ - - get currentTransformation() { return this._currentTransformation; } - set currentTransformation(currentTransformation) { if (['translate', 'scale', 'rotate'].indexOf(currentTransformation) === -1) { console.error('Paper.SelectionWidget: Invalid transformation type: ' + currentTransformation); @@ -61851,14 +60506,13 @@ class SelectionWidget { this._currentTransformation = currentTransformation; } } + /** * Build a new SelectionWidget GUI around some items. * @param {number} boxRotation - the rotation of the selection GUI. Optional, defaults to 0 * @param {paper.Item[]} items - the items to build the GUI around * @param {paper.Point} pivot - the pivot point that the selection rotates around. Defaults to (0,0) */ - - build(args) { if (!args) args = {}; if (!args.boxRotation) args.boxRotation = 0; @@ -61870,33 +60524,25 @@ class SelectionWidget { this._boundingBox = this._calculateBoundingBox(); this.item.remove(); this.item.removeChildren(); - if (this._ghost) { this._ghost.remove(); } - if (this._pivotPointHandle) { this._pivotPointHandle.remove(); } - if (this._itemsInSelection.length > 0) { this._center = this._calculateBoundingBoxOfItems(this._itemsInSelection).center; - this._buildGUI(); - this.layer.addChild(this.item); } } + /** * */ - - startTransformation(item) { this._ghost = this._buildGhost(); - this._layer.addChild(this._ghost); - if (item.data.handleType === 'rotation') { this.currentTransformation = 'rotate'; } else if (item.data.handleType === 'scale') { @@ -61904,15 +60550,13 @@ class SelectionWidget { } else { this.currentTransformation = 'translate'; } - this._ghost.data.initialPosition = this._ghost.position; this._ghost.data.scale = new paper.Point(1, 1); } + /** * */ - - updateTransformation(item, e) { if (this.currentTransformation === 'translate') { this._ghost.position = this._ghost.position.add(e.delta); @@ -61923,28 +60567,24 @@ class SelectionWidget { currentPoint = currentPoint.rotate(-this.boxRotation, this.pivot); var pivotToLastPointVector = lastPoint.subtract(this.pivot); var pivotToCurrentPointVector = currentPoint.subtract(this.pivot); - var scaleAmt = pivotToCurrentPointVector.divide(pivotToLastPointVector); // Lock scaling in a direction if the side handles are being dragged. + var scaleAmt = pivotToCurrentPointVector.divide(pivotToLastPointVector); + // Lock scaling in a direction if the side handles are being dragged. if (item.data.handleEdge === 'topCenter' || item.data.handleEdge === 'bottomCenter') { scaleAmt.x = 1.0; } - if (item.data.handleEdge === 'leftCenter' || item.data.handleEdge === 'rightCenter') { scaleAmt.y = 1.0; - } // Holding shift locks aspect ratio - + } + // Holding shift locks aspect ratio if (e.modifiers.shift) { scaleAmt.y = scaleAmt.x; } - this._ghost.data.scale = this._ghost.data.scale.multiply(scaleAmt); this._ghost.matrix = new paper.Matrix(); - this._ghost.rotate(-this.boxRotation); - this._ghost.scale(this._ghost.data.scale.x, this._ghost.data.scale.y, this.pivot); - this._ghost.rotate(this.boxRotation); } else if (this.currentTransformation === 'rotate') { var lastPoint = e.point.subtract(e.delta); @@ -61954,51 +60594,41 @@ class SelectionWidget { var pivotToLastPointAngle = pivotToLastPointVector.angle; var pivotToCurrentPointAngle = pivotToCurrentPointVector.angle; var rotation = pivotToCurrentPointAngle - pivotToLastPointAngle; - this._ghost.rotate(rotation, this.pivot); - this.boxRotation += rotation; } } + /** * */ - - finishTransformation(item) { if (!this._currentTransformation) return; - this._ghost.remove(); - if (this.currentTransformation === 'translate') { var d = this._ghost.position.subtract(this._ghost.data.initialPosition); - this.translateSelection(d); } else if (this.currentTransformation === 'scale') { this.scaleSelection(this._ghost.data.scale); } else if (this.currentTransformation === 'rotate') { this.rotateSelection(this._ghost.rotation); } - this._currentTransformation = null; } + /** * */ - - translateSelection(delta) { this._itemsInSelection.forEach(item => { item.position = item.position.add(delta); }); - this.pivot = this.pivot.add(delta); } + /** * */ - - scaleSelection(scale) { this._itemsInSelection.forEach(item => { item.rotate(-this.boxRotation, this.pivot); @@ -62006,24 +60636,20 @@ class SelectionWidget { item.rotate(this.boxRotation, this.pivot); }); } + /** * */ - - rotateSelection(angle) { this._itemsInSelection.forEach(item => { item.rotate(angle, this.pivot); }); } - _buildGUI() { this.item.addChild(this._buildBorder()); - if (this._itemsInSelection.length > 1) { this.item.addChildren(this._buildItemOutlines()); } - let guiElements = []; guiElements.push(this._buildRotationHotspot('topLeft')); guiElements.push(this._buildRotationHotspot('topRight')); @@ -62045,7 +60671,6 @@ class SelectionWidget { child.data.isSelectionBoxGUI = true; }); } - _buildBorder() { var border = new paper.Path.Rectangle({ name: 'border', @@ -62058,7 +60683,6 @@ class SelectionWidget { border.data.isBorder = true; return border; } - _buildItemOutlines() { return this._itemsInSelection.map(item => { var clone = item.clone({ @@ -62071,13 +60695,12 @@ class SelectionWidget { to: bounds.bottomRight, strokeWidth: SelectionWidget.BOX_STROKE_WIDTH, strokeColor: SelectionWidget.BOX_STROKE_COLOR - }); //border.rotate(-this.boxRotation, this._center); - + }); + //border.rotate(-this.boxRotation, this._center); border.remove(); return border; }); } - _buildScalingHandle(edge) { var handle = this._buildHandle({ name: edge, @@ -62086,10 +60709,8 @@ class SelectionWidget { fillColor: SelectionWidget.HANDLE_FILL_COLOR, strokeColor: SelectionWidget.HANDLE_STROKE_COLOR }); - return handle; } - _buildPivotPointHandle() { var handle = this._buildHandle({ name: 'pivot', @@ -62098,11 +60719,9 @@ class SelectionWidget { fillColor: SelectionWidget.PIVOT_FILL_COLOR, strokeColor: SelectionWidget.PIVOT_STROKE_COLOR }); - handle.locked = true; return handle; } - _buildHandle(args) { if (!args) console.error('_createHandle: args is required'); if (!args.name) console.error('_createHandle: args.name is required'); @@ -62124,9 +60743,9 @@ class SelectionWidget { circle.data.handleEdge = args.name; return circle; } - _buildRotationHotspot(cornerName) { // Build the not-yet-rotated hotspot, which starts out like this: + // | // +---+ // | | @@ -62134,30 +60753,31 @@ class SelectionWidget { // | | // +------+ // | + var r = SelectionWidget.ROTATION_HOTSPOT_RADIUS / paper.view.zoom; var hotspot = new paper.Path([new paper.Point(0, 0), new paper.Point(0, r), new paper.Point(r, r), new paper.Point(r, -r), new paper.Point(-r, -r), new paper.Point(-r, 0)]); hotspot.fillColor = SelectionWidget.ROTATION_HOTSPOT_FILLCOLOR; hotspot.position.x = this.boundingBox[cornerName].x; - hotspot.position.y = this.boundingBox[cornerName].y; // Orient the rotation handles in the correct direction, even if the selection is flipped + hotspot.position.y = this.boundingBox[cornerName].y; + // Orient the rotation handles in the correct direction, even if the selection is flipped hotspot.rotate({ 'topRight': 0, 'bottomRight': 90, 'bottomLeft': 180, 'topLeft': 270 - }[cornerName]); // Some metadata. + }[cornerName]); + // Some metadata. hotspot.data.handleType = 'rotation'; hotspot.data.handleEdge = cornerName; return hotspot; } - _buildGhost() { var ghost = new paper.Group({ insert: false, applyMatrix: false }); - this._itemsInSelection.forEach(item => { var outline = item.clone(); outline.remove(); @@ -62172,7 +60792,6 @@ class SelectionWidget { outline2.strokeWidth = SelectionWidget.GHOST_STROKE_WIDTH; ghost.addChild(outline2); }); - var boundsOutline = new paper.Path.Rectangle({ from: this.boundingBox.topLeft, to: this.boundingBox.bottomRight, @@ -62186,24 +60805,19 @@ class SelectionWidget { ghost.opacity = 0.5; return ghost; } - _calculateBoundingBox() { if (this._itemsInSelection.length === 0) { return new paper.Rectangle(); } - var center = this._calculateBoundingBoxOfItems(this._itemsInSelection).center; - var itemsForBoundsCalc = this._itemsInSelection.map(item => { var clone = item.clone(); clone.rotate(-this.boxRotation, center); clone.remove(); return clone; }); - return this._calculateBoundingBoxOfItems(itemsForBoundsCalc); } - _calculateBoundingBoxOfItems(items) { var bounds = null; items.forEach(item => { @@ -62211,9 +60825,7 @@ class SelectionWidget { }); return bounds || new paper.Rectangle(); } - } - ; SelectionWidget.BOX_STROKE_WIDTH = 1; SelectionWidget.BOX_STROKE_COLOR = 'rgba(100,150,255,1.0)'; @@ -62250,6 +60862,7 @@ paper.PaperScope.inject({ * You should have received a copy of the GNU General Public License * along with Paper.js-drawing-tools. If not, see . */ + paper.SelectionBox = class { /* * @@ -62265,79 +60878,64 @@ paper.SelectionBox = class { }); this._mode = 'intersects'; } + /* * */ - - start(point) { this._active = true; this._start = point; this._end = point; - this._rebuildBox(); } + /* * */ - - drag(point) { this._end = point; - this._rebuildBox(); } + /* * */ - - end(point) { this._end = point; this._active = false; - this._rebuildBox(); - this._box.remove(); - this._items = this._itemsInBox(this._box); } + /* * */ - - get items() { return this._items; } + /* * */ - - get active() { return this._active; } + /* * */ - - get mode() { return this._mode; } - set mode(mode) { if (mode !== 'contains' && mode !== 'intersects') { throw new Error("SelectionBox.mode: invalid mode"); } - this._mode = mode; } - _rebuildBox() { this._box.remove(); - this._box = new this.paper.Path.Rectangle({ from: this._start, to: this._end, @@ -62345,16 +60943,13 @@ paper.SelectionBox = class { strokeColor: 'black' }); } - _itemsInBox(box) { var checkItems = []; - this._getSelectableLayers().forEach(layer => { layer.children.forEach(child => { checkItems.push(child); }); }); - var items = []; checkItems.forEach(item => { if (this.mode === 'contains') { @@ -62369,7 +60964,6 @@ paper.SelectionBox = class { }); return items; } - _shapesIntersect(itemA, itemB) { if (itemA instanceof this.paper.Group) { var intersects = false; @@ -62384,20 +60978,17 @@ paper.SelectionBox = class { } else { var shapesDoIntersect = itemB.intersects(itemA); var boundsContain = itemB.bounds.contains(itemA.bounds); - if (shapesDoIntersect || boundsContain) { return true; } } } - _getSelectableLayers() { var self = this; return this.paper.project.layers.filter(layer => { return !layer.locked; }); } - }; paper.PaperScope.inject({ SelectionBox: paper.SelectionBox @@ -62428,6 +61019,7 @@ paper.PaperScope.inject({ by zrispo (github.com/zrispo) (zach@wickeditor.com) */ + paper.Path.inject({ potrace: function (args) { var self = this; @@ -62438,14 +61030,12 @@ paper.Path.inject({ var raster = this.rasterize(finalRasterResolution); raster.remove(); var rasterDataURL = raster.toDataURL(); - if (rasterDataURL === 'data:,') { args.done(null); - } // https://oov.github.io/potrace/ - + } + // https://oov.github.io/potrace/ var img = new Image(); - img.onload = function () { var svg = potrace.fromImage(img).toSVG(1 / args.resolution); var potracePath = paper.project.importSVG(svg); @@ -62456,7 +61046,6 @@ paper.Path.inject({ potracePath.children[0].closed = true; args.done(potracePath.children[0]); }; - img.src = rasterDataURL; } }); @@ -62478,6 +61067,7 @@ paper.Path.inject({ * You should have received a copy of the GNU General Public License * along with Paper.js-drawing-tools. If not, see . */ + (function () { var editElem = $('