/** * {} * @author yutent * @date 2024/03/07 15:33:05 */ import eve from './eve.js' import { Animation } from './animation.js' import mina from './mina.js' import { is } from './utils.js' let mmax = Math.max, mmin = Math.min // Set let Set = function (items) { this.items = [] this.bindings = {} this.length = 0 this.type = 'set' if (items) { for (let i = 0, ii = items.length; i < ii; i++) { if (items[i]) { this[this.items.length] = this.items[this.items.length] = items[i] this.length++ } } } }, setproto = Set.prototype /*\ * Set.push [ method ] ** * Adds each argument to the current set = (object) original element \*/ setproto.push = function () { let item, len for (let i = 0, ii = arguments.length; i < ii; i++) { item = arguments[i] if (item) { len = this.items.length this[len] = this.items[len] = item this.length++ } } return this } /*\ * Set.pop [ method ] ** * Removes last element and returns it = (object) element \*/ setproto.pop = function () { this.length && delete this[this.length--] return this.items.pop() } /*\ * Set.forEach [ method ] ** * Executes given function for each element in the set * * If the function returns `false`, the loop stops running. ** - callback (function) function to run - thisArg (object) context object for the callback = (object) Set object \*/ setproto.forEach = function (callback, thisArg) { for (let i = 0, ii = this.items.length; i < ii; i++) { if (callback.call(thisArg, this.items[i], i) === false) { return this } } return this } /** * Animates each element in set in sync. * ** - attrs (object) key-value pairs of destination attributes - duration (number) duration of the animation in milliseconds - easing (function) #optional easing function from @mina or custom - callback (function) #optional callback function that executes when the animation ends * or - animation (array) array of animation parameter for each element in set in format `[attrs, duration, easing, callback]` > Usage | // animate all elements in set to radius 10 | set.animate({r: 10}, 500, mina.easein); | // or | // animate first element to radius 10, but second to radius 20 and in different time | set.animate([{r: 10}, 500, mina.easein], [{r: 20}, 1500, mina.easein]); = (SnapElement) the current element */ setproto.animate = function (attrs, ms, easing, callback) { if (typeof easing == 'function' && !easing.length) { callback = easing easing = mina.linear } if (attrs instanceof Animation) { callback = attrs.callback easing = attrs.easing ms = easing.dur attrs = attrs.attr } let args = arguments if (is(attrs, 'array') && is(args[args.length - 1], 'array')) { let each = true } let begin, handler = function () { if (begin) { this.b = begin } else { begin = this.b } }, cb = 0, set = this, callbacker = callback && function () { if (++cb == set.length) { callback.call(this) } } return this.forEach(function (el, i) { eve.once('snap.animcreated.' + el.id, handler) if (each) { args[i] && el.animate.apply(el, args[i]) } else { el.animate(attrs, ms, easing, callbacker) } }) } /** * Removes all children of the set. * */ setproto.remove = function () { while (this.length) { this.pop().remove() } return this } /** * Specifies how to handle a specific attribute when applied * to a set. * ** - attr (string) attribute name - callback (function) function to run * or - attr (string) attribute name - element (SnapElement) specific element in the set to apply the attribute to * or - attr (string) attribute name - element (SnapElement) specific element in the set to apply the attribute to - eattr (string) attribute on the element to bind the attribute to = (object) Set object */ setproto.bind = function (attr, a, b) { let data = {} if (typeof a == 'function') { this.bindings[attr] = a } else { let aname = b || attr this.bindings[attr] = function (v) { data[aname] = v a.attr(data) } } return this } /** * Equivalent of @SnapElement.attr. */ setproto.attr = function (value) { let unbound = {} for (let k in value) { if (this.bindings[k]) { this.bindings[k](value[k]) } else { unbound[k] = value[k] } } for (let i = 0, ii = this.items.length; i < ii; i++) { this.items[i].attr(unbound) } return this } /** * Removes all elements from the set */ setproto.clear = function () { while (this.length) { this.pop() } } /** * Removes range of elements from the set ** * index (number) position of the deletion * count (number) number of element to remove * insertion… (object) #optional elements to insert */ setproto.splice = function (index, count, insertion) { index = index < 0 ? mmax(this.length + index, 0) : index count = mmax(0, mmin(this.length - index, count)) let tail = [], todel = [], args = [], i for (i = 2; i < arguments.length; i++) { args.push(arguments[i]) } for (i = 0; i < count; i++) { todel.push(this[index + i]) } for (; i < this.length - index; i++) { tail.push(this[index + i]) } let arglen = args.length for (i = 0; i < arglen + tail.length; i++) { this.items[index + i] = this[index + i] = i < arglen ? args[i] : tail[i - arglen] } i = this.items.length = this.length -= count - arglen while (this[i]) { delete this[i++] } return new Set(todel) } /** * Removes given element from the set ** * element (object) element to remove */ setproto.exclude = function (el) { for (let i = 0, ii = this.length; i < ii; i++) if (this[i] == el) { this.splice(i, 1) return true } return false } /** * Inserts set elements after given element. ** * element (object) set will be inserted after this element */ setproto.insertAfter = function (el) { let i = this.items.length while (i--) { this.items[i].insertAfter(el) } return this } /** * Union of all bboxes of the set. See @SnapElement.getBBox. */ setproto.getBBox = function () { let x = [], y = [], x2 = [], y2 = [] for (let i = this.items.length; i--; ) if (!this.items[i].removed) { let box = this.items[i].getBBox() x.push(box.x) y.push(box.y) x2.push(box.x + box.width) y2.push(box.y + box.height) } x = mmin.apply(0, x) y = mmin.apply(0, y) x2 = mmax.apply(0, x2) y2 = mmax.apply(0, y2) return { x: x, y: y, x2: x2, y2: y2, width: x2 - x, height: y2 - y, cx: x + (x2 - x) / 2, cy: y + (y2 - y) / 2 } } /** * Creates a clone of the set. */ setproto.clone = function (s) { s = new Set() for (let i = 0, ii = this.items.length; i < ii; i++) { s.push(this.items[i].clone()) } return s } setproto.type = 'set' /** * Creates a set and fills it with list of arguments. ** = (object) New Set object | let r = paper.rect(0, 0, 10, 10), | s1 = createSet(), // empty set | s2 = createSet(r, paper.circle(100, 100, 20)); // prefilled set */ export function createSet(...args) { let set = new Set() if (args.length) { set.push.apply(set, args) } return set }