/* * ********************************************************************************************************************* * * !! * .F88X * X8888Y * .}888888N; * i888888N; .:! .I$WI: * R888888I .'N88~ i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8& * .R888888I .;N8888~ .X8' "8I.!,/8" !%NY8`"8I8~~8>,88I * +888888N; .8888888Y "&&8Y.}8, * ./888888N; .R888888Y .'}~ .>}'.`+> i}! "i' +/' .'i~ !11,.:">, .~]! .i}i * ~888888%: .I888888l .]88~`1/iY88Ii+1'.R$8$8]"888888888> Y8$ W8E X8E W8888'188Il}Y88$* * 18888888 E8888881 .]W%8$`R8X'&8%++N8i,8N%N8+l8%` .}8N:.R$RE%N88N%N$K$R 188,FE$8%~Y88I * .E888888I .i8888888' .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~ * 8888888I .,N888888~ ~88i"8W,!N8*.I88.}888%F,i$88"F88" 888:E8X.>88!i88>`888*.}Fl1]*}1YKi' * i888888N' I888Y ]88;/EX*IFKFK88X K8R .l8W 88Y ~88}'88E&%8W.X8N``]88!.$8K .:W8I * .i888888N; I8Y .&8$ .X88! i881.:%888>I88 ;88] +88+.';;;;:.Y88X 18N.,88l .+88/ * .:R888888I * .&888888I Copyright (c) 2009-2020. 博锐尚格科技股份有限公司 * ~8888' * .!88~ All rights reserved. * * ********************************************************************************************************************* */ import { SMouseButton, SMouseEvent, SObject, SMatrix } from "@persagy-web/base"; import { SPainter, SPoint, SRect } from "@persagy-web/draw"; import { SGraphScene } from "./SGraphScene"; import { SPath } from "@persagy-web/draw/lib"; /** * Graph 图形引擎 Item 类 * * @author 庞利祥 */ export class SGraphItem extends SObject { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 属性 /** 场景对象 */ private _scene: SGraphScene | null = null; get scene(): SGraphScene | null { if (null != this._parent) { return this._parent.scene; } else { return this._scene; } } set scene(v: SGraphScene | null) { this._scene = v; this.update(); } /** parent 属性存值函数 */ private _parent: SGraphItem | null = null; get parent(): SGraphItem | null { return this._parent; } set parent(v: SGraphItem | null) { // 如果 parent 未变更 if (this.parent == v) { return; } // 如果原 parent 不为空 if (this._parent != null) { // 将节点从原 parent 节点中摘除 let i = this._parent.children.indexOf(this); this._parent.children.splice(i, 1); } this._parent = v; this.update(); // 如果新 parent 不为空 if (this._parent != null) { // 将节点加入到新 parent 节点中 this._parent.children.push(this); this._parent.children.sort(SGraphItem.sortItemZOrder); } } /** 子节点 */ children: SGraphItem[] = []; /** Z 轴顺序 */ private _zOrder: number = 0; get zOrder(): number { return this._zOrder; } set zOrder(v: number) { this._zOrder = v; if (this._parent != null) { this._parent.children.sort(SGraphItem.sortItemZOrder); } this.update(); } /** 位置 */ pos: SPoint = new SPoint(0, 0); /** X 轴坐标 */ get x(): number { return this.pos.x; } set x(v: number) { if (this.pos.x == v) { return; } let old = this.pos.x; this.pos.x = v; this.update(); } /** Y 轴坐标 */ get y(): number { return this.pos.y; } set y(v: number) { if (this.pos.y == v) { return; } let old = this.pos.y; this.pos.y = v; this.update(); } /** 缩放比例 */ scale: number = 1; /** 放缩反比例 */ private _inverseScale: number = 1; get inverseScale(): number { return this._inverseScale; } /** 旋转角度 */ _rotate: number = 0; get rotate(): number { return this._rotate; } set rotate(v: number) { this._rotate = v; this.update(); } /** 是否可见 */ private _visible: boolean = true; get visible(): boolean { return this._visible; } set visible(v: boolean) { this._visible = v; this.update(); } /** 透明度 */ private _globalAlpha = 1; get globalAlpha(): number { return this._globalAlpha; } // Get number set globalAlpha(v: number) { this._globalAlpha = v; this.update(); } // Set number /** 是否可以移动 */ moveable: boolean = false; /** 是否正在移动 */ protected _isMoving = false; /** 是否可用 */ protected _enabled: boolean = true; get enabled(): boolean { return this._enabled; } set enabled(value: boolean) { // 如果选择状态未变更 if (this.enabled == value) { return; } this._enabled = value; this.update(); } /** 是否可被选中 */ selectable = false; /** 是否被选中 */ protected _selected = false; get selected(): boolean { return this._selected && this.selectable && this.enabled; } set selected(value: boolean) { // 如果选择状态未变更 if (this.selected == value) { return; } this._selected = value; this.update(); } /** 是否可修改大小 */ _resizeable: boolean = true; /** 选中时是否显示选择器 */ showSelect: boolean = true; /** 是否进行变形 */ isTransform = true; /** 鼠标按下时位置 */ protected _mouseDownPos = new SPoint(); /** 鼠标样式 */ cursor: string = "default"; /** 保存上一次的 grabItem */ private _lastGrab: SGraphItem | null = null; /** 外层自定义数据 */ private _data: any | null = null; set data(d: any) { this._data = d; this.initData(); this.update(); } get data(): any | null { return this._data; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 函数 /** * 构造函数 * * @param parent 指向父对象 */ constructor(parent: SGraphItem | null = null) { super(); if (parent) { this.parent = parent; } } /** * Item 绘制框架 * * @param painter 绘制对象 * @param rect 绘制区域 */ onPaint(painter: SPainter, rect: SRect): void { this.onDraw(painter); for (let item of this.children) { // 如果 item 不可见 if (!item.visible) { continue; } // item 所占矩阵与要刷新的矩阵相交则刷新,否则不刷 // if (item.boundingRect()--rect){ // // } // 保存画布状态 painter.save(); try { // item 位移到指定位置绘制 painter.translate(item.x, item.y); painter.scale(item.scale, item.scale); painter.rotate(item.rotate); // painter.globalAlpha = item.globalAlpha; // 如果不进行变形处理,则取消 painter 的变型操作 if (!item.isTransform) { let matrix = painter.worldTransform; item._inverseScale = 1.0 / matrix.a; painter.scale(item._inverseScale, item._inverseScale); } // rect 调整 宽度高度取最小值 根据 pos 位移 // 绘制 item item.onPaint(painter, rect); } catch (e) { console.log(e); } // 恢复画布状态 painter.restore(); } } /** * Item 绘制操作 * * @param painter 绘制对象 */ onDraw(painter: SPainter): void {} /** * 隐藏对象 */ hide(): void { this.visible = false; } /** * 显示对象 */ show(): void { this.visible = true; } /** * 更新 Item */ update(): void { if (null != this.scene) { const view = this.scene.view; if (null != view) { view.update(); } } } /** * Item 对象边界区域 * * @return 对象边界区域 */ boundingRect(): SRect { return new SRect(0, 0, 10, 10); } /** * 移动 item 到指定位置 * * @param x 新位置的 x 坐标 * @param y 新位置的 y 坐标 */ moveTo(x: number, y: number): void { this.x = x; this.y = y; } // moveTo() /** * 判断 item 是否包含点 x, y * * @param x 横坐标(当前 item) * @param y 纵坐标(当前 item) * @return item 是否包含点 x, y */ contains(x: number, y: number): boolean { return this.boundingRect().contains(x, y); } /** * 获得 item 的路径节点列表。(该节点被加载到场景中,如果未被加载到场景中,计算会出错) * * @return item 的路径节点列表 */ itemPath(): SGraphItem[] { if (this.parent != null) { let list = this.parent.itemPath(); list.push(this); return list; } return [this]; } /** * 将场景中的 x, y 坐标转换成 item 坐标。(该节点被加载到场景中,如果未被加载到场景中,计算会出错) * * @param x 场景中的横坐标 * @param y 场景中的纵坐标 * @return 在 item 中的坐标 */ mapFromScene(x: number, y: number): SPoint { const m = this.scene2itemMattrix(); return new SPoint(x, y).matrixTransform(m.inversed()); } /** * 将 item 中的 x, y 坐标转换成场景坐标。(该节点被加载到场景中,如果未被加载到场景中,计算会出错) * * @param x item 中的横坐标 * @param y item 中的纵坐标 * @return 在场景中的坐标 */ mapToScene(x: number, y: number): SPoint { if (this.parent == null) { return new SPoint(x, y); } const m = this.scene2itemMattrix(); return new SPoint(x, y).matrixTransform(m); } /** * 场景对象到 item 对象的转换矩阵 * * @return 转换矩阵 */ scene2itemMattrix(): SMatrix { let m = new SMatrix(); let list = this.itemPath(); for (let item of list) { m.translate(item.x, item.y); m.scale(item.scale, item.scale); m.rotate(item.rotate); // 如果不进行变形处理,则取消 painter 的变型操作 if (!item.isTransform) { m.scale(item._inverseScale, item._inverseScale); } } return m; } // ================================================================================================================= // 事件 /** * 鼠标单击事件 * * @param event 保存事件参数 * @return 是否处理事件 */ onClick(event: SMouseEvent): boolean { for (let i = this.children.length - 1; i >= 0; i--) { let item = this.children[i]; if (!this.acceptEvent() || !item.visible) { continue; } let ce = SGraphItem.toChildMouseEvent(item, event); if (item.contains(ce.x, ce.y) && item.onClick(ce)) { // 如果点在子项目上且子项目处理了事件 return true; } } return false; } /** * 鼠标双击事件 * * @param event 保存事件参数 * @return 是否处理事件 */ onDoubleClick(event: SMouseEvent): boolean { for (let i = this.children.length - 1; i >= 0; i--) { let item = this.children[i]; if (!this.acceptEvent() || !item.visible) { continue; } let ce = SGraphItem.toChildMouseEvent(item, event); if (item.contains(ce.x, ce.y) && item.onDoubleClick(ce)) { // 如果点在子项目上且子项目处理了事件 return true; } } return false; } /** * 鼠标按下事件 * * @param event 保存事件参数 * @return 是否处理事件 */ onMouseDown(event: SMouseEvent): boolean { for (let i = this.children.length - 1; i >= 0; i--) { try { let item = this.children[i]; if (!this.acceptEvent() || !item.visible) { continue; } let ce = SGraphItem.toChildMouseEvent(item, event); if (item.contains(ce.x, ce.y) && item.onMouseDown(ce)) { // 如果点在子项目上且子项目处理了事件 return true; } } catch (e) { console.log(e); } } this.$emit("onMouseDown", event); // 移动 if (this.moveable) { this._mouseDownPos = new SPoint(event.x, event.y); this._isMoving = true; if (this.scene) { this._lastGrab = this.scene.grabItem; } this.grabItem(this); return true; } return false; } /** * 鼠标移动事件 * * @param event 保存事件参数 * @return 是否处理事件 */ onMouseMove(event: SMouseEvent): boolean { for (let i = this.children.length - 1; i >= 0; i--) { let item = this.children[i]; if (!this.acceptEvent() || !item.visible) { continue; } let ce = SGraphItem.toChildMouseEvent(item, event); if (item.contains(ce.x, ce.y) && item.onMouseMove(ce)) { // 如果点在子项目上且子项目处理了事件 return true; } } this.$emit("onMouseMove", event); if (this.scene && this.scene.view) { this.scene.view.cursor = this.cursor; } if ( event.buttons & SMouseButton.LeftButton && this.moveable && this._isMoving ) { let old = new SPoint(this.pos.x, this.pos.y); const mp = this.toParentChange( event.x - this._mouseDownPos.x, event.y - this._mouseDownPos.y ); this.moveTo(this.pos.x + mp.x, this.pos.y + mp.y); this.$emit("onMove", old, this.pos); } // 处理 hover const scene = this.scene; if (null != scene) { if (scene.hoverItem == null || scene.hoverItem !== this) { if (scene.hoverItem != null) { scene.hoverItem.onMouseLeave(event); } this.onMouseEnter(event); scene.hoverItem = this; } } return true; } /** * 释放鼠标事件 * * @param event 保存事件参数 * @return 是否处理事件 */ onMouseUp(event: SMouseEvent): boolean { for (let i = this.children.length - 1; i >= 0; i--) { let item = this.children[i]; if (!this.acceptEvent() || !item.visible) { continue; } let ce = SGraphItem.toChildMouseEvent(item, event); if (item.contains(ce.x, ce.y) && item.onMouseUp(ce)) { // 如果点在子项目上且子项目处理了事件 return true; } } // 选择 let select = false; // 是否可选中 if (this.selectable) { select = this.clickSelect(event); } this.$emit("onMouseUp", event); this._isMoving = false; this.releaseItem(); if (this.scene) { this.scene.grabItem = this._lastGrab; } return select; } /** * 鼠标进入事件 * * @param event 保存事件参数 * @return 是否处理事件 */ onMouseEnter(event: SMouseEvent): boolean { this.$emit("onMouseEnter", event); return false; } /** * 鼠标离开事件 * * @param event 保存事件参数 * @return 是否处理事件 */ onMouseLeave(event: SMouseEvent): boolean { this.$emit("onMouseLeave", event); return false; } /** * 上下文菜单事件 * * @param event 事件参数 * @return 是否处理事件 */ onContextMenu(event: SMouseEvent): boolean { for (let i = this.children.length - 1; i >= 0; i--) { let item = this.children[i]; if (!this.acceptEvent() || !item.visible) { continue; } let ce = SGraphItem.toChildMouseEvent(item, event); if (item.contains(ce.x, ce.y) && item.onContextMenu(ce)) { // 如果点在子项目上且子项目处理了事件 return true; } } this.$emit("onContextMenu", event); return false; } /** * 按键按下事件 * * @param event 事件参数 */ onKeyDown(event: KeyboardEvent): void {} /** * 按键 press 事件 * * @param event 事件参数 */ onKeyPress(event: KeyboardEvent): void {} /** * 按键松开事件 * * @param event 事件参数 */ onKeyUp(event: KeyboardEvent): void {} /** * 取消操作 item 事件 */ cancelOperate(): void {} /** * 返回 item 对应的数据对象 * * @return item 数据 */ toData(): any | null { return null; } /** * 初始化数据 */ initData(): void { // do nothing } /** * 移动 item 后的处理,计算其他信息,将原点置为场景原点 * * @param x x 坐标 * @param y y 坐标 */ moveToOrigin(x: number, y: number): void { if (this.children && this.children.length) { this.children.forEach((it): void => { it.moveToOrigin(x, y); }); } } // ================================================================================================================= // 私有方法 /** * 按 ZOrder 排序 * * @param a 比较元素 1 * @param b 比较元素 2 * @return 2 个元素 zOrder 的差值 */ private static sortItemZOrder(a: SGraphItem, b: SGraphItem): number { return a.zOrder - b.zOrder; } /** * 鼠标事件转子对象鼠标事件 * * @param child 子对象 * @param event 事件参数 * @return 子对象鼠标事件 */ private static toChildMouseEvent( child: SGraphItem, event: SMouseEvent ): SMouseEvent { let ce = new SMouseEvent(event); ce.matrix.translate(child.x, child.y); ce.matrix.scale(child.scale, child.scale); ce.matrix.rotate(0, 0, child.rotate); if (!child.isTransform) { ce.matrix.scale(child._inverseScale, child._inverseScale); } const mp = new SPoint(event.offsetX, event.offsetY).matrixTransform( ce.matrix.inversed() ); ce.x = mp.x; ce.y = mp.y; return ce; } /** * 锁定 item * * @param item 被锁定的 item */ protected grabItem(item: SGraphItem): void { if (this.scene != null) { this.scene.grabItem = item; } } /** * 释放被锁定的 item */ protected releaseItem(): void { if (this.scene != null) { this.scene.grabItem = null; } } /** * 计算点在父节点的位置 * * @param x X 轴 * @param y Y 轴 * @return 在父节点的位置 */ protected toParentChange(x: number, y: number): SPoint { const m = new SMatrix(); m.scale(this.scale, this.scale); if (!this.isTransform) { m.scale(this._inverseScale, this._inverseScale); } m.rotate(this.rotate); const mp = new SPoint(x, y).matrixTransform(m); return new SPoint(mp.x, mp.y); } /** * 判断是否处理事件 * * @return 是否处理事件 */ private acceptEvent(): boolean { return this.visible && this.enabled; } /** * 点选 item 对象 * * @param event 事件参数 * @return 是否处理事件 */ private clickSelect(event: SMouseEvent): boolean { // 如果 Item 不可被选中,或没有按下鼠标左键,则直接返回 if (!this.selectable) { // || event.buttons != SMouseButton.LeftButton return false; } const scene = this.scene; if (scene == null) { return false; } // 如果按下 Ctrl 键,只改变当前 item 的选择标志 if (event.ctrlKey) { scene.selectContainer.toggleItem(this); } else { scene.selectContainer.setItem(this); } return true; } }