diff --git a/src/ui/core_extend_patch.js b/src/ui/core_extend_patch.js index b603c497b..4ef62b1e0 100644 --- a/src/ui/core_extend_patch.js +++ b/src/ui/core_extend_patch.js @@ -245,6 +245,22 @@ export default function extendCorePatch() return ops; }; + CABLES.Patch.prototype.getAllAnimPorts = function () + { + const ports = []; + const ops = gui.corePatch().ops; + for (let i = 0; i < ops.length; i++) + { + for (let j = 0; j < ops[i].portsIn.length; j++) + { + if (ops[i].portsIn[j].isAnimated())ports.push(ops[i].portsIn[j]); + + } + } + + return ports; + }; + CABLES.Patch.prototype.reloadOp = function (objName, cb, refOldOp) { let count = 0; diff --git a/src/ui/gltimeline/gltimeline.js b/src/ui/gltimeline/gltimeline.js index 7b0a815b8..197cd0f1e 100644 --- a/src/ui/gltimeline/gltimeline.js +++ b/src/ui/gltimeline/gltimeline.js @@ -74,6 +74,7 @@ export default class GlTimeline extends Events #layout = 0; + /** @type {Array} */ #selectedKeys = []; hoverKeyRect = null; @@ -253,10 +254,13 @@ export default class GlTimeline extends Events this.updateAllElements(); } - snapTime(t) + /** + * @param {number} time + */ + snapTime(time) { - if (this.cfg.restrictToFrames) t = Math.floor(t * this.fps) / this.fps; - return t; + if (this.cfg.restrictToFrames) time = Math.floor(time * this.fps) / this.fps; + return time; } toggleGraphLayout() @@ -316,8 +320,8 @@ export default class GlTimeline extends Events this.#rectSelect.setPosition(this.#lastXnoButton, this.#lastYnoButton, -1); this.#rectSelect.setSize(x - this.#lastXnoButton, y - this.#lastYnoButton); - } + if (y < this.getFirstLinePosy()) { gui.corePatch().timer.setTime(this.snapTime(this.view.pixelToTime(e.offsetX - this.titleSpace) + this.view.offset)); @@ -411,6 +415,18 @@ export default class GlTimeline extends Events this.updateAllElements(); } + getSelectedKeysBoundsTime() + { + let min = 999999; + let max = -999999; + for (let i = 0; i < this.#selectedKeys.length; i++) + { + min = Math.min(min, this.#selectedKeys[i].time); + max = Math.max(max, this.#selectedKeys[i].time); + } + return { "min": min, "max": max }; + } + deleteSelectedKeys() { for (let i = 0; i < this.#selectedKeys.length; i++) @@ -440,8 +456,9 @@ export default class GlTimeline extends Events { if (!e.pointerType) return; - if (this.hoverKeyRect == null) - this.unSelectAllKeys(); + if (e.buttons == 1) + if (this.hoverKeyRect == null) + this.unSelectAllKeys(); try { this.#cgl.canvas.setPointerCapture(e.pointerId); } catch (er) { this._log.log(er); } @@ -714,16 +731,68 @@ export default class GlTimeline extends Events /** * @param {ClipboardEvent} event */ - copy(event) + copy(event = null) { const obj = { "keys": this.serializeSelectedKeys() }; - const objStr = JSON.stringify(obj); - console.log("copy", obj); + if (event) + { + const objStr = JSON.stringify(obj); + event.clipboardData.setData("text/plain", objStr); + event.preventDefault(); + } + return obj; - event.clipboardData.setData("text/plain", objStr); - event.preventDefault(); + } + /** + * @param {ClipboardEvent} event + */ + cut(event) + { + this.copy(event); + this.deleteSelectedKeys(); + } + + /** + * @param {Array} keys + * @param {boolean} setCursorTime=true + */ + deserializeKeys(keys, setCursorTime = true) + { + + let minTime = Number.MAX_VALUE; + for (let i in keys) + { + minTime = Math.min(minTime, keys[i].t); + } + let notfoundallAnims = false; + + for (let i = 0; i < keys.length; i++) + { + console.log("add key..."); + const k = keys[i]; + if (setCursorTime) + { + k.t = k.t - minTime + this.cursorTime; + } + + let found = false; + for (let j = 0; j < this.#tlAnims.length; j++) + { + let an = this.#tlAnims[j].getAnimByName(k.animName); + if (an) + { + an.addKey(new CABLES.ANIM.Key(keys[i])); + found = true; + } + } + + if (!found) + notfoundallAnims = true; + + } + return notfoundallAnims; } /** @@ -744,35 +813,7 @@ export default class GlTimeline extends Events { if (json.keys) { - - let minTime = Number.MAX_VALUE; - for (let i in json.keys) - { - minTime = Math.min(minTime, json.keys[i].t); - } - let notfoundallAnims = false; - - for (let i = 0; i < json.keys.length; i++) - { - const k = json.keys[i]; - k.t = k.t - minTime + this.cursorTime; - - let found = false; - for (let j = 0; j < this.#tlAnims.length; j++) - { - let an = this.#tlAnims[j].getAnimByName(k.animName); - if (an) - { - an.addKey(new CABLES.ANIM.Key(json.keys[i])); - found = true; - } - } - - if (!found) - notfoundallAnims = true; - - } - + const notfoundallAnims = this.deserializeKeys(json.keys); if (notfoundallAnims) { notifyWarn("could not find all anims for pasted keys"); @@ -782,6 +823,13 @@ export default class GlTimeline extends Events notify(json.keys.length + " keys pasted"); } + const animPorts = gui.corePatch().getAllAnimPorts(); + for (let i = 0; i < animPorts.length; i++) + { + + if (animPorts[i].anim) + animPorts[i].anim.removeDuplicates(); + } // anim.sortKeys(); // for (let i in anim.keys) @@ -804,4 +852,12 @@ export default class GlTimeline extends Events return true; } + duplicateSelectedKeys() + { + const o = this.copy(); + + this.deserializeKeys(o.keys, false); + + } + } diff --git a/src/ui/gltimeline/gltlanimline.js b/src/ui/gltimeline/gltlanimline.js index 4210731eb..967d52ac2 100644 --- a/src/ui/gltimeline/gltlanimline.js +++ b/src/ui/gltimeline/gltlanimline.js @@ -60,7 +60,7 @@ export default class glTlAnimLine extends Events /** * @param {GlTimeline} glTl - * @param {Array} ports + * @param {Array} ports * @param {Object} options */ constructor(glTl, ports, options = {}) @@ -101,7 +101,8 @@ export default class glTlAnimLine extends Events const lid = anim.addEventListener("onChange", () => { - keys.init(); + if (!keys.isDragging()) + keys.init(); }); this.#animChangeListeners.push({ "id": lid, "anim": anim }); diff --git a/src/ui/gltimeline/gltlkeys.js b/src/ui/gltimeline/gltlkeys.js index a15ac05da..b1b201cfe 100644 --- a/src/ui/gltimeline/gltlkeys.js +++ b/src/ui/gltimeline/gltlkeys.js @@ -44,6 +44,8 @@ export default class glTlKeys extends Events #points = []; #options = {}; + #dragStarted = false; + /** * @param {GlTimeline} glTl * @param {Anim} anim @@ -75,7 +77,11 @@ export default class glTlKeys extends Events this.points = []; this.init(); + } + isDragging() + { + return this.#dragStarted; } get anim() @@ -256,27 +262,46 @@ export default class glTlKeys extends Events kr.on(GlRect.EVENT_DRAGEND, () => { this.#anim.sortKeys(); + this.#dragStarted = false; }); kr.on(GlRect.EVENT_DRAGSTART, (rect, x, y, button, e) => { - startDrag = this.#glTl.view.pixelToTime(e.offsetX); + + if (button == 1 && !this.#dragStarted) + { + this.#dragStarted = true; + startDrag = this.#glTl.view.pixelToTime(e.offsetX); + + console.log("dragstart", button, e.shiftKey); + + if (e.shiftKey) + { + this.#glTl.duplicateSelectedKeys(); + } + + } }); kr.on(GlRect.EVENT_DRAG, (rect, offx, offy, button, e) => { if (this.#glTl.selectRect) return; - const offTime = this.#glTl.view.pixelToTime(e.offsetX) - startDrag; - startDrag = this.#glTl.view.pixelToTime(e.offsetX); - - if (this.#glTl.getNumSelectedKeys() > 0) + if (button == 1) { - this.#glTl.moveSelectedKeysDelta(offTime); - this.#anim.sortKeys(); - } - this.update(0, 0); + const offTime = this.#glTl.view.pixelToTime(e.offsetX) - startDrag; + startDrag = this.#glTl.snapTime(this.#glTl.view.pixelToTime(e.offsetX)); + + if (this.#glTl.getNumSelectedKeys() > 0) + { + this.#glTl.moveSelectedKeysDelta(this.#glTl.snapTime(offTime)); + this.#anim.sortKeys(); + } + + this.update(0, 0); + + } }); this.#keyRects.push(kr);