diff --git a/packages/x6/src/research/attr/define.ts b/packages/x6/src/research/attr/define.ts index 6bc5c53e84a..bdd7639d675 100644 --- a/packages/x6/src/research/attr/define.ts +++ b/packages/x6/src/research/attr/define.ts @@ -298,7 +298,8 @@ export namespace Attribute { const firstChild = node.firstChild as Element if (firstChild && firstChild.tagName.toUpperCase() === 'TITLE') { // Update an existing title - (firstChild as SVGTitleElement).textContent = title + const titleElem = firstChild as SVGTitleElement + titleElem.textContent = title } else { // Create a new title const titleNode = document.createElementNS( diff --git a/packages/x6/src/research/cell/cell.ts b/packages/x6/src/research/cell/cell.ts deleted file mode 100644 index 0d026dcb0f0..00000000000 --- a/packages/x6/src/research/cell/cell.ts +++ /dev/null @@ -1,554 +0,0 @@ -/* tslint:disable:variable-name */ - -import { KV } from '../../types' -import { Basecoat } from '../../entity' -import { JSONObject, JSONExt, ArrayExt, ObjectExt } from '../../util' -import { - IChange, - ChildChange, - AttrsChange, - MarkupChange, - VisibleChange, -} from '../change' -import { Node } from './node' -import { Model } from '../kernel/model' -import { Easing } from './easing' -import { Rectangle } from '../../geometry' -import { DomUtil } from '../../dom' -import { Interpolation } from './interpolation' - -export class Cell extends Basecoat { - public readonly id: string | number - public model: Model - - protected _markup: string - protected _attrs: JSONObject - protected _parent: Cell | null - protected _children: Cell[] | null - protected _visible: boolean - - constructor() { - super() - this._visible = true - } - - isNode(): this is Node { - return false - } - - isEdge() { - return false - } - - // #region markup - - get markup() { - return this._markup - } - - set markup(value: string) { - this.setMarkup(value) - } - - setMarkup(markup: string, options: Cell.SetOptions = {}) { - if (options.force || this.markup !== markup) { - if (options.silent) { - this._markup = markup - } else { - this.execute(new MarkupChange(this, markup), 'markup', options) - } - } - } - - // #endregion - - // #region attrs - - get arrts() { - return this._attrs - } - - set attrs(value: JSONObject) { - this.setAttrs(value) - } - - setAttrs(attrs: JSONObject, options: Cell.SetOptions = {}) { - if (options.force || JSONExt.deepEqual(this.arrts, attrs)) { - if (options.silent) { - this._attrs = JSONExt.deepCopy(attrs) - } else { - this.execute(new AttrsChange(this, attrs), 'attrs', options) - } - } - } - - // #endregion - - // #region parent & children - - get parent() { - return this._parent - } - - get children() { - return this._children - } - - hasParent() { - return this.parent != null - } - - getParent() { - return this.parent - } - - isParentOf(child: Cell | null): boolean { - return child != null && child.getParent() === this - } - - isChildOf(parent: Cell | null): boolean { - return parent != null && this.getParent() === parent - } - - getChildren() { - return this.children - } - - eachChild( - iterator: (child: Cell, index: number, children: Cell[]) => void, - context?: any, - ) { - ArrayExt.forEach(this.children, iterator, context) - return this - } - - filterChild( - filter: (cell: Cell, index: number, arr: Cell[]) => boolean, - thisArg?: any, - ): Cell[] { - return ArrayExt.filter(this.children, filter, thisArg) - } - - getChildCount() { - return this.children == null ? 0 : this.children.length - } - - getChildIndex(child: Cell) { - return this.children == null ? -1 : this.children.indexOf(child) - } - - getChildAt(index: number) { - return this.children != null && index >= 0 ? this.children[index] : null - } - - getAncestors(options: { deep?: boolean } = {}) { - const ancestors = [] - let parent = this.getParent() - while (parent) { - ancestors.push(parent) - parent = options.deep !== false ? parent.getParent() : null - } - return ancestors - } - - getDescendants( - options: { - deep?: boolean - breadthFirst?: boolean - } = {}, - ): Cell[] { - if (options.deep !== false) { - // breadthFirst algorithm - if (options.breadthFirst) { - const cells = [] - const queue = this.getDescendants() - - while (queue.length > 0) { - const parent = queue.shift()! - cells.push(parent) - queue.push(...parent.getDescendants()) - } - return cells - } - - // depthFirst algorithm - { - const cells = this.getDescendants() - cells.forEach(cell => { - cells.push(...cell.getDescendants(options)) - }) - return cells - } - } - - return this.getChildren() || [] - } - - isDescendantOf( - ancestor: Cell | null, - options: { deep?: boolean } = {}, - ): boolean { - if (ancestor == null) { - return false - } - - if (options.deep !== false) { - let current = this.getParent() - while (current) { - if (current === ancestor) { - return true - } - current = current.getParent() - } - - return false - } - - return this.isChildOf(ancestor) - } - - isAncestorOf( - descendant: Cell | null, - options: { deep?: boolean } = {}, - ): boolean { - if (descendant == null) { - return false - } - - return descendant.isDescendantOf(this, options) - } - - contains(cell: Cell | null) { - return this.isAncestorOf(cell) - } - - setParent(parent: Cell, options: Cell.SetOptions = {}) { - if (options.force || this.parent !== parent) { - if (options.silent || parent == null) { - this._parent = parent - } else { - parent.insertChild(this, undefined, options) - } - } - } - - removeFromParent(options: Cell.SetOptions = {}) { - if (this.parent != null) { - const index = this.parent.getChildIndex(this) - this.parent.removeChildAt(index, options) - } - } - - insertChild( - child: Cell | null, - index?: number, - options: Cell.SetOptions = {}, - ) { - if (child != null && child !== this) { - let pos = index - if (pos == null) { - pos = this.getChildCount() - if (child.getParent() === this) { - pos -= 1 - } - } - - const changed = this !== child.getParent() - if (options.silent) { - child.removeFromParent(options) - child.setParent(this, options) - if (this._children == null) { - this._children = [] - this._children.push(child) - } else { - this._children.splice(pos, 0, child) - } - } else { - this.execute(new ChildChange(this, child, pos), 'add', options) - } - - if (changed) { - // this.updateEdgeParents(child) - } - } - - return this - } - - removeChild(child: Cell, options: Cell.SetOptions = {}) { - const index = this.getChildIndex(child) - return this.removeChildAt(index, options) - } - - removeChildAt(index: number, options: Cell.SetOptions = {}) { - let child = null - - if (this.children != null && index >= 0) { - child = this.getChildAt(index) - if (child != null) { - if (options.silent) { - child.setParent(null as any, options) - this.children.splice(index, 1) - } else { - this.execute(new ChildChange(null, child, index), 'remove', options) - } - } - } - - return child - } - - // #endregion - - // #region visible - - get visible() { - return this._visible - } - - set visible(value: boolean) { - this.setVisible(value) - } - - setVisible(visible: boolean, options: Cell.SetOptions = {}) { - if (options.force || visible !== this.visible) { - if (options.silent) { - this._visible = visible - } else { - this.execute(new VisibleChange(this, visible), 'visible', options) - } - } - } - - isVisible() { - return this.visible - } - - show(options: Cell.SetOptions = {}) { - if (!this.visible) { - this.setVisible(true, options) - } - } - - hide(options: Cell.SetOptions = {}) { - if (this.visible) { - this.setVisible(false, options) - } - } - - toggleVisible(options: Cell.SetOptions = {}) { - if (this.visible) { - this.hide(options) - } else { - this.show(options) - } - } - - // #endregion - - // #region transition - - protected transitionIds: { [path: string]: number } - - transition( - path: string, - target: T, - options: Cell.TransitionOptions = {}, - delim = '/', - ) { - const opts: Cell.TransitionOptions = { - delay: 10, - duration: 100, - easing: 'linear', - ...options, - } - - let easing = Easing.linear - if (opts.easing != null) { - if (typeof opts.easing === 'string') { - easing = Easing[opts.easing] - } else { - easing = opts.easing - } - } - - const current = ObjectExt.getByPath(this.attrs, path, delim) - - let interpolate: any - if (typeof target === 'object') { - interpolate = Interpolation.object(current, target as any) - } else if (typeof target === 'number') { - interpolate = Interpolation.number(current, target) - } else if (typeof target === 'string') { - if (target[0] === '#') { - interpolate = Interpolation.color(current, target) - } else { - interpolate = Interpolation.unit(current, target) - } - } - - let startTime = 0 - - const setter = () => { - let id - let val - - const now = new Date().getTime() - if (startTime === 0) { - startTime = now - } - - const elaspe = now - startTime - let progress = elaspe / opts.duration! - if (progress < 1) { - this.transitionIds[path] = id = DomUtil.requestAnimationFrame(setter) - } else { - progress = 1 - delete this.transitionIds[path] - } - - val = interpolate(easing(progress)) - options.transitionId = id - - this.set(path, val) - - if (id == null) { - this.trigger('transition:end', this, path) - } - } - - const initiator = (transition: FrameRequestCallback) => { - this.stopTransitions(path, delim) - this.transitionIds[path] = DomUtil.requestAnimationFrame(transition) - this.trigger('transition:begin', this, path) - } - - return setTimeout(() => { - initiator(setter) - }, options.delay) - } - - getTransitions() { - return Object.keys(this.transitionIds) - } - - stopTransitions(path: string, delim: string = '/') { - const arr = path && path.split(delim) - - Object.keys(this.transitionIds) - .filter(key => - JSONExt.deepEqual(arr, key.split(delim).slice(0, arr.length)), - ) - .forEach(key => { - DomUtil.cancelAnimationFrame(this.transitionIds[key]) - delete this.transitionIds[key] - this.trigger('transition:end', this, key) - }) - - return this - } - - protected set(path: string, val: any) {} - - // #endregion - - toJSON() {} - - clone() {} - - getBBox(options: { deep?: boolean } = {}) { - return new Rectangle(0, 0, 0, 0) - } - - protected execute(change: IChange, name: string, data?: KV) { - this.trigger('change', change) - if (this.model) { - this.model.execute(change, name, data) - } else { - change.execute() - } - this.trigger('changed', change) - } - - protected batchUpdate(name: string, update: () => T, data: KV = {}) { - if (this.model) { - return this.model.batchUpdate(name, update, data) - } - - return update() - } -} - -export namespace Cell { - export interface SetOptions extends KV { - force?: boolean - silent?: boolean - } - - export interface TransitionOptions extends KV { - delay?: number - duration?: number - easing?: Easing.Names | Easing.Func - } -} - -export namespace Cell { - export function getCellsBBox( - cells: Cell[], - options: { deep?: boolean } = {}, - ) { - const bbox = new Rectangle(0, 0, 0, 0) - cells.forEach(cell => { - let rect = cell.getBBox(options) - if (rect) { - const rotation = (cell as Node).rotation - if (rotation != null) { - rect = rect.bbox(rotation) - } - - bbox.union(rect) - } - }) - - return bbox - } -} - -export namespace Cell { - const options = { force: true, silent: true } - - export function executeChildChange( - child: Cell, - parent: Cell | null, - index?: number, - ) { - const previous = child.getParent() - if (parent != null) { - if (parent !== previous || previous.getChildIndex(child) !== index) { - parent.insertChild(child, index, { ...options }) - } - } else if (previous != null) { - previous.removeChild(child, { ...options }) - } - - return previous - } - - export function executeMarkupChange(cell: Cell, markup: string) { - const previous = cell.markup - this.setMarkup(markup, { ...options }) - return previous - } - - export function executeAttrsChange(cell: Cell, attrs: JSONObject) { - const previous = cell.attrs - this.setAttrs(attrs, { ...options }) - return previous - } - - export function executeVisibleChange(cell: Cell, visible: boolean) { - const previous = cell.visible - this.setVisible(visible, { ...options }) - return previous - } -} diff --git a/packages/x6/src/research/cell/easing.ts b/packages/x6/src/research/cell/easing.ts deleted file mode 100644 index 51897653560..00000000000 --- a/packages/x6/src/research/cell/easing.ts +++ /dev/null @@ -1,277 +0,0 @@ -import { FunctionKeys } from 'utility-types' - -export namespace Easing { - export type Func = (t: number) => number - export type Names = FunctionKeys -} - -export namespace Easing { - export function linear(t: number) { - return t - } - - // Slight acceleration from zero to full speed - export function easeInSine(t: number) { - return -1 * Math.cos(t * (Math.PI / 2)) + 1 - } - - // Slight deceleration at the end - export function easeOutSine(t: number) { - return Math.sin(t * (Math.PI / 2)) - } - - // Slight acceleration at beginning and slight deceleration at end - export function easeInOutSine(t: number) { - return -0.5 * (Math.cos(Math.PI * t) - 1) - } - - // Accelerating from zero velocity - export function easeInQuad(t: number) { - return t * t - } - - // Decelerating to zero velocity - export function easeOutQuad(t: number) { - return t * (2 - t) - } - - // Acceleration until halfway, then deceleration - export function easeInOutQuad(t: number) { - return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t - } - - // Accelerating from zero velocity - export function easeInCubic(t: number) { - return t * t * t - } - - // Decelerating to zero velocity - export function easeOutCubic(t: number) { - const t1 = t - 1 - return t1 * t1 * t1 + 1 - } - - // Acceleration until halfway, then deceleration - export function easeInOutCubic(t: number) { - return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1 - } - - // Accelerating from zero velocity - export function easeInQuart(t: number) { - return t * t * t * t - } - - // Decelerating to zero velocity - export function easeOutQuart(t: number) { - const t1 = t - 1 - return 1 - t1 * t1 * t1 * t1 - } - - // Acceleration until halfway, then deceleration - export function easeInOutQuart(t: number) { - const t1 = t - 1 - return t < 0.5 ? 8 * t * t * t * t : 1 - 8 * t1 * t1 * t1 * t1 - } - - // Accelerating from zero velocity - export function easeInQuint(t: number) { - return t * t * t * t * t - } - - // Decelerating to zero velocity - export function easeOutQuint(t: number) { - const t1 = t - 1 - return 1 + t1 * t1 * t1 * t1 * t1 - } - - // Acceleration until halfway, then deceleration - export function easeInOutQuint(t: number) { - const t1 = t - 1 - return t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * t1 * t1 * t1 * t1 * t1 - } - - // Accelerate exponentially until finish - export function easeInExpo(t: number) { - if (t === 0) { - return 0 - } - - return Math.pow(2, 10 * (t - 1)) - } - - // Initial exponential acceleration slowing to stop - export function easeOutExpo(t: number) { - if (t === 1) { - return 1 - } - - return -Math.pow(2, -10 * t) + 1 - } - - // Exponential acceleration and deceleration - export function easeInOutExpo(t: number) { - if (t === 0 || t === 1) { - return t - } - - const scaledTime = t * 2 - const scaledTime1 = scaledTime - 1 - - if (scaledTime < 1) { - return 0.5 * Math.pow(2, 10 * scaledTime1) - } - - return 0.5 * (-Math.pow(2, -10 * scaledTime1) + 2) - } - - // Increasing velocity until stop - export function easeInCirc(t: number) { - const scaledTime = t / 1 - return -1 * (Math.sqrt(1 - scaledTime * t) - 1) - } - - // Start fast, decreasing velocity until stop - export function easeOutCirc(t: number) { - const t1 = t - 1 - return Math.sqrt(1 - t1 * t1) - } - - // Fast increase in velocity, fast decrease in velocity - export function easeInOutCirc(t: number) { - const scaledTime = t * 2 - const scaledTime1 = scaledTime - 2 - - if (scaledTime < 1) { - return -0.5 * (Math.sqrt(1 - scaledTime * scaledTime) - 1) - } - - return 0.5 * (Math.sqrt(1 - scaledTime1 * scaledTime1) + 1) - } - - // Slow movement backwards then fast snap to finish - export function easeInBack(t: number, magnitude = 1.70158) { - return t * t * ((magnitude + 1) * t - magnitude) - } - - // Fast snap to backwards point then slow resolve to finish - export function easeOutBack(t: number, magnitude = 1.70158) { - const scaledTime = t / 1 - 1 - - return ( - scaledTime * scaledTime * ((magnitude + 1) * scaledTime + magnitude) + 1 - ) - } - - // Slow movement backwards, fast snap to past finish, slow resolve to finish - export function easeInOutBack(t: number, magnitude = 1.70158) { - const scaledTime = t * 2 - const scaledTime2 = scaledTime - 2 - - const s = magnitude * 1.525 - - if (scaledTime < 1) { - return 0.5 * scaledTime * scaledTime * ((s + 1) * scaledTime - s) - } - - return 0.5 * (scaledTime2 * scaledTime2 * ((s + 1) * scaledTime2 + s) + 2) - } - - // Bounces slowly then quickly to finish - export function easeInElastic(t: number, magnitude = 0.7) { - if (t === 0 || t === 1) { - return t - } - - const scaledTime = t / 1 - const scaledTime1 = scaledTime - 1 - - const p = 1 - magnitude - const s = (p / (2 * Math.PI)) * Math.asin(1) - - return -( - Math.pow(2, 10 * scaledTime1) * - Math.sin(((scaledTime1 - s) * (2 * Math.PI)) / p) - ) - } - - // Fast acceleration, bounces to zero - export function easeOutElastic(t: number, magnitude = 0.7) { - const p = 1 - magnitude - const scaledTime = t * 2 - - if (t === 0 || t === 1) { - return t - } - - const s = (p / (2 * Math.PI)) * Math.asin(1) - return ( - Math.pow(2, -10 * scaledTime) * - Math.sin(((scaledTime - s) * (2 * Math.PI)) / p) + - 1 - ) - } - - // Slow start and end, two bounces sandwich a fast motion - export function easeInOutElastic(t: number, magnitude = 0.65) { - const p = 1 - magnitude - - if (t === 0 || t === 1) { - return t - } - - const scaledTime = t * 2 - const scaledTime1 = scaledTime - 1 - - const s = (p / (2 * Math.PI)) * Math.asin(1) - - if (scaledTime < 1) { - return ( - -0.5 * - (Math.pow(2, 10 * scaledTime1) * - Math.sin(((scaledTime1 - s) * (2 * Math.PI)) / p)) - ) - } - - return ( - Math.pow(2, -10 * scaledTime1) * - Math.sin(((scaledTime1 - s) * (2 * Math.PI)) / p) * - 0.5 + - 1 - ) - } - - // Bounce to completion - export function easeOutBounce(t: number) { - const scaledTime = t / 1 - - if (scaledTime < 1 / 2.75) { - return 7.5625 * scaledTime * scaledTime - } - if (scaledTime < 2 / 2.75) { - const scaledTime2 = scaledTime - 1.5 / 2.75 - return 7.5625 * scaledTime2 * scaledTime2 + 0.75 - } - if (scaledTime < 2.5 / 2.75) { - const scaledTime2 = scaledTime - 2.25 / 2.75 - return 7.5625 * scaledTime2 * scaledTime2 + 0.9375 - } - { - const scaledTime2 = scaledTime - 2.625 / 2.75 - return 7.5625 * scaledTime2 * scaledTime2 + 0.984375 - } - } - - // Bounce increasing in velocity until completion - export function easeInBounce(t: number) { - return 1 - easeOutBounce(1 - t) - } - - // Bounce in and bounce out - export function easeInOutBounce(t: number) { - if (t < 0.5) { - return easeInBounce(t * 2) * 0.5 - } - - return easeOutBounce(t * 2 - 1) * 0.5 + 0.5 - } -} diff --git a/packages/x6/src/research/cell/index.ts b/packages/x6/src/research/cell/index.ts deleted file mode 100644 index c764889eb88..00000000000 --- a/packages/x6/src/research/cell/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './cell' -export * from './node' diff --git a/packages/x6/src/research/cell/interpolation.ts b/packages/x6/src/research/cell/interpolation.ts deleted file mode 100644 index eb375815c29..00000000000 --- a/packages/x6/src/research/cell/interpolation.ts +++ /dev/null @@ -1,61 +0,0 @@ -export namespace Interpolation { - export function number(a: number, b: number) { - const d = b - a - return (t: number) => { - return a + d * t - } - } - - export function object( - a: { [key: string]: number }, - b: { [key: string]: number }, - ) { - const keys = Object.keys(a) - return (t: number) => { - const ret: { [key: string]: number } = {} - for (let i = keys.length - 1; i !== -1; i -= 1) { - const key = keys[i] - ret[key] = a[key] + (b[key] - a[key]) * t - } - return ret - } - } - - export function unit(a: string, b: string) { - const reg = /(-?[0-9]*.[0-9]*)(px|em|cm|mm|in|pt|pc|%)/ - const ma = reg.exec(a) - const mb = reg.exec(b) - - const pb = mb ? mb[1] : '' - const aa = ma ? +ma[1] : 0 - const bb = mb ? +mb[1] : 0 - - const index = pb.indexOf('.') - const precision = index > 0 ? pb[1].length - index - 1 : 0 - - const d = bb - aa - const u = ma ? ma[2] : '' - - return (t: number) => { - return (aa + d * t).toFixed(precision) + u - } - } - - export function color(a: string, b: string) { - const ca = parseInt(a.slice(1), 16) - const cb = parseInt(b.slice(1), 16) - const ra = ca & 0x0000ff - const rd = (cb & 0x0000ff) - ra - const ga = ca & 0x00ff00 - const gd = (cb & 0x00ff00) - ga - const ba = ca & 0xff0000 - const bd = (cb & 0xff0000) - ba - - return (t: number) => { - const r = (ra + rd * t) & 0x000000ff - const g = (ga + gd * t) & 0x0000ff00 - const b = (ba + bd * t) & 0x00ff0000 - return `#${((1 << 24) | r | g | b).toString(16).slice(1)}` - } - } -} diff --git a/packages/x6/src/research/cell/node.ts b/packages/x6/src/research/cell/node.ts deleted file mode 100644 index 9bd621363de..00000000000 --- a/packages/x6/src/research/cell/node.ts +++ /dev/null @@ -1,417 +0,0 @@ -/* tslint:disable:variable-name */ - -import { Size } from '../../types' -import { Cell } from './cell' -import { Point, Rectangle, Angle } from '../../geometry' -import { PositionChange, SizeChange } from '../change' -import { RotationChange } from '../change/rotation-change' - -export class Node extends Cell { - _size: Size = { width: 1, height: 1 } - _position: Point.PointLike = { x: 0, y: 0 } - _rotation: number = 0 - - get size() { - return { ...this._size } - } - - get rotation() { - return Angle.normalize(this._rotation || 0) - } - - get position() { - return { ...this._position } - } - - isNode() { - return true - } - - getPosition(options: { relative?: boolean } = {}) { - if (options.relative) { - const parent = this.getParent() - if (parent != null && parent.isNode()) { - const currentPosition = this.position - const parentPosition = parent.position - - return { - x: currentPosition.x - parentPosition.x, - y: currentPosition.y - parentPosition.y, - } - } - } - - return this.position - } - - setPosition(x: number, y: number, options: Node.PositionOptions = {}) { - if (options.relative) { - const parent = this.getParent() - if (parent != null && parent.isNode()) { - const parentPosition = parent.position - x += parentPosition.x // tslint:disable-line - y += parentPosition.y // tslint:disable-line - } - } - - if (options.deep) { - const pos = this.position - this.translate(x - pos.x, y - pos.y, options) - } else { - if (options.silent) { - this._position = { x, y } - } else { - this.execute(new PositionChange(this, { x, y }), 'position', options) - } - } - - return this - } - - translate( - tx: number = 0, - ty: number = 0, - options: Node.TranslateOptions = {}, - ) { - if (tx === 0 && ty === 0) { - return this - } - - const position = this.position - - if (options.restrictedArea != null) { - // We are restricting the translation for the node itself only. We get - // the bounding box of the node including all its children. - // All children have to be translated the exact same way as the node. - const bbox = this.getBBox({ deep: true }) - const ra = options.restrictedArea - // - - - - - - - - - - - - -> ra.x + ra.width - // - - - -> position.x | - // -> bbox.x - // ▓▓▓▓▓▓▓ | - // ░░░░░░░▓▓▓▓▓▓▓ - // ░░░░░░░░░ | - // ▓▓▓▓▓▓▓▓░░░░░░░ - // ▓▓▓▓▓▓▓▓ | - // <-dx-> | restricted area right border - // <-width-> | ░ translated node - // <- - bbox.width - -> ▓ child node - const dx = position.x - bbox.x - const dy = position.y - bbox.y - - // Finds the maximal/minimal coordinates that the node can be - // translated while complies the restrictions. - const x = Math.max( - ra.x + dx, - Math.min(ra.x + ra.width + dx - bbox.width, position.x + tx), - ) - const y = Math.max( - ra.y + dy, - Math.min(ra.y + ra.height + dy - bbox.height, position.y + ty), - ) - - // Recalculates the translation taking the restrictions into account. - tx = x - position.x // tslint:disable-line - ty = y - position.y // tslint:disable-line - } - - const translatedPosition = { - x: position.x + tx, - y: position.y + ty, - } - - options.tx = tx - options.ty = ty - - // if (options.transition) { - // if (!isObject(options.transition)) options.transition = {} - - // this.transition( - // 'position', - // translatedPosition, - // assign({}, options.transition, { - // valueFunction: interpolate.object, - // }), - // ) - - // // Recursively call `translate()` on all the embeds cells. - // this.eachChild(child => child.translate(tx, ty, options)) - // } else { - // this.startBatch('translate', options) - // this.store.set('position', translatedPosition, options) - // this.eachChild(child => (child as Node).translate(tx, ty, options)) - // this.stopBatch('translate', options) - // } - - if (options.silent) { - this._position = translatedPosition - this.eachChild(child => { - if (child.isNode()) { - child.translate(tx, ty, options) - } - }) - } else { - this.batchUpdate('translate', () => { - this.execute( - new PositionChange(this, { ...translatedPosition }), - 'translate', - options, - ) - this.eachChild(child => { - if (child.isNode()) { - child.translate(tx, ty, options) - } - }) - }) - } - - return this - } - - setSize(size: Size, options?: Node.ResizeOptions): this - setSize(width: number, height: number, options?: Node.ResizeOptions): this - setSize( - width: number | Size, - height?: number | Node.ResizeOptions, - options?: Node.ResizeOptions, - ) { - let w: number - let h: number - let opts: Node.ResizeOptions - - if (typeof width === 'object') { - w = width.width - h = width.height - opts = height as Node.ResizeOptions - } else { - w = width - h = height as number - opts = options as Node.ResizeOptions - } - - this.resize(w, h, opts) - return this - } - - resize(width: number, height: number, options: Node.ResizeOptions = {}) { - if (options.direction) { - const currentSize = this.size - const angle = Angle.normalize(this.rotation || 0) - - switch (options.direction) { - case 'left': - case 'right': - // Don't change height when resizing horizontally. - height = currentSize.height // tslint:disable-line - break - - case 'top': - case 'bottom': - // Don't change width when resizing vertically. - width = currentSize.width // tslint:disable-line - break - } - - let quadrant = { - 'top-right': 0, - right: 0, - 'top-left': 1, - top: 1, - 'bottom-left': 2, - left: 2, - 'bottom-right': 3, - bottom: 3, - }[options.direction] - - if (options.absolute) { - // We are taking the element's rotation into account - quadrant += Math.floor((angle + 45) / 90) - quadrant %= 4 - } - - let fixedPoint: Point - const bbox = this.getBBox() - if (quadrant === 0) { - fixedPoint = bbox.getBottomLeft() - } else if (quadrant === 1) { - fixedPoint = bbox.getCorner() - } else if (quadrant === 2) { - fixedPoint = bbox.getTopRight() - } else if (quadrant === 3) { - fixedPoint = bbox.getOrigin() - } - - // Find an image of the previous indent point. This is the position, - // where is the point actually located on the screen. - const imageFixedPoint = fixedPoint! - .clone() - .rotate(-angle, bbox.getCenter()) - - // Every point on the element rotates around a circle with the centre of - // rotation in the middle of the element while the whole element is being - // rotated. That means that the distance from a point in the corner of - // the element (supposed its always rect) to the center of the element - // doesn't change during the rotation and therefore it equals to a - // distance on un-rotated element. We can find the distance as - // DISTANCE = (WIDTH/2)^2 + (HEIGHT/2)^2)^0.5. - const radius = Math.sqrt(width * width + height * height) / 2 - - // Now we are looking for an angle between x-axis and the line starting - // at image of fixed point and ending at the center of the element. We - // call this angle `alpha`. - - // The image of a fixed point is located in n-th quadrant. For each - // quadrant passed going anti-clockwise we have to add 90 degrees. - // Note that the first quadrant has index 0. - // - // 3 | 2 - // --c-- Quadrant positions around the element's center `c` - // 0 | 1 - // - let alpha = (quadrant * Math.PI) / 2 - - // Add an angle between the beginning of the current quadrant (line parallel with x-axis or y-axis - // going through the center of the element) and line crossing the indent of the fixed point and the center - // of the element. This is the angle we need but on the un-rotated element. - alpha += Math.atan(quadrant % 2 === 0 ? height / width : width / height) - - // Lastly we have to deduct the original angle the element was rotated by and that's it. - alpha -= Angle.toRad(angle) - - // With this angle and distance we can easily calculate the centre of the un-rotated element. - // Note that fromPolar constructor accepts an angle in radians. - const center = Point.fromPolar(radius, alpha, imageFixedPoint) - - // The top left corner on the un-rotated element has to be half a width - // on the left and half a height to the top from the center. This will - // be the origin of rectangle we were looking for. - const origin = center.clone().translate(width / -2, height / -2) - - if (options.silent) { - this._size = { width, height } - this._position = origin - } else { - const name = 'resize' - this.batchUpdate(name, () => { - this.execute(new SizeChange(this, { width, height }), name, options) - this.execute(new PositionChange(this, origin), name, options) - }) - } - } else { - if (options.silent) { - this._size = { width, height } - } else { - this.execute(new SizeChange(this, { width, height }), 'resize', options) - } - } - - return this - } - - scale( - sx: number, - sy: number, - origin?: Point | Point.PointLike, - options: Node.ScaleOptions = {}, - ) { - this.batchUpdate('scale', () => { - const bbox = this.getBBox().scale(sx, sy, origin) - this.setPosition(bbox.x, bbox.y, options) - this.resize(bbox.width, bbox.height, options) - }) - return this - } - - rotate( - angle: number, - origin?: Point | Point.PointLike, - options: Node.RorateOptions = {}, - ) { - if (origin != null) { - const size = this.size - const position = this.position - const center = this.getBBox().getCenter() - center.rotate(this.rotation - angle, origin) - const dx = center.x - size.width / 2 - position.x - const dy = center.y - size.height / 2 - position.y - this.batchUpdate('rotate', () => { - this.setPosition(position.x + dx, position.y + dy, options) - this.rotate(angle, undefined, options) - }) - } else { - const rotation = options.relative ? (this.rotation + angle) % 360 : angle - if (options.silent) { - this._rotation = rotation - } else { - this.execute(new RotationChange(this, rotation), 'rotate', options) - } - } - - return this - } - - getBBox(options: { deep?: boolean } = {}) { - if (options.deep) { - const cells = this.getDescendants({ deep: true, breadthFirst: true }) - cells.push(this) - return Cell.getCellsBBox(cells) - } - - const size = this.size - const position = this.position - return new Rectangle(position.x, position.y, size.width, size.height) - } -} - -export namespace Node { - export interface PositionOptions extends Cell.SetOptions { - deep?: boolean - relative?: boolean - } - - export interface TranslateOptions extends Cell.SetOptions { - transition?: boolean - restrictedArea?: Rectangle.RectangleLike - } - - export interface ResizeOptions extends Cell.SetOptions { - absolute?: boolean - direction?: - | 'left' - | 'right' - | 'top' - | 'top-left' - | 'top-right' - | 'bottom' - | 'bottom-left' - | 'bottom-right' - } - - export interface RorateOptions extends Cell.SetOptions { - relative?: boolean - } - - export interface ScaleOptions extends Cell.SetOptions {} -} - -export namespace Node { - const options = { force: true, silent: true } - - export function executePositionChange(node: Node, position: Point.PointLike) { - const previous = node.position - node.setPosition(position.x, position.y, { ...options }) - return previous - } - - export function executeSizeChange(node: Node, size: Size) { - const previous = node.size - node.setSize(size, { ...options }) - return previous - } - - export function executeRotationChange(node: Node, rotation: number) { - const previous = node.rotation - node.rotate(rotation, undefined, { ...options }) - return previous - } -}