|
@@ -0,0 +1,592 @@
|
|
|
+import { SMouseButton, SMouseEvent, SObject } from "@saga-web/base/lib";
|
|
|
+import { SPainter, SPoint, SRect } from "@saga-web/draw/lib";
|
|
|
+import { SGraphScene } from "./SGraphScene";
|
|
|
+
|
|
|
+/**
|
|
|
+ * Graph图形引擎Item类
|
|
|
+ *
|
|
|
+ * @author 庞利祥(sybotan@126.com)
|
|
|
+ */
|
|
|
+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;
|
|
|
+ }
|
|
|
+ } // Get scene
|
|
|
+ set scene(v: SGraphScene | null) {
|
|
|
+ this._scene = v;
|
|
|
+ this.update();
|
|
|
+ } // Set scene
|
|
|
+ /** parent属性存值函数 */
|
|
|
+ private _parent: SGraphItem | null = null;
|
|
|
+ get parent(): SGraphItem | null {
|
|
|
+ return this._parent;
|
|
|
+ } // Get 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);
|
|
|
+ }
|
|
|
+ } // Set parent()
|
|
|
+
|
|
|
+ /** 子节点 */
|
|
|
+ children: SGraphItem[] = [];
|
|
|
+
|
|
|
+ /** Z轴顺序 */
|
|
|
+ private _zOrder: number = 0;
|
|
|
+ get zOrder(): number {
|
|
|
+ return this._zOrder;
|
|
|
+ } // Get zOrder
|
|
|
+ set zOrder(v: number) {
|
|
|
+ this._zOrder = v;
|
|
|
+ if (this._parent != null) {
|
|
|
+ this._parent.children.sort(SGraphItem.sortItemZOrder);
|
|
|
+ }
|
|
|
+ this.update();
|
|
|
+ } // Set zOrder
|
|
|
+
|
|
|
+ /** 位置 */
|
|
|
+ pos: SPoint = new SPoint(0, 0);
|
|
|
+ /** X轴坐标 */
|
|
|
+ get x(): number {
|
|
|
+ return this.pos.x;
|
|
|
+ } // Get x
|
|
|
+ set x(v: number) {
|
|
|
+ if (this.pos.x == v) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let old = this.pos.x;
|
|
|
+ this.pos.x = v;
|
|
|
+ this.update();
|
|
|
+ } // Set x
|
|
|
+ /** Y轴坐标 */
|
|
|
+ get y(): number {
|
|
|
+ return this.pos.y;
|
|
|
+ } // Get y
|
|
|
+ set y(v: number) {
|
|
|
+ if (this.pos.y == v) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let old = this.pos.y;
|
|
|
+ this.pos.y = v;
|
|
|
+ this.update();
|
|
|
+ } // Set y
|
|
|
+ /** 缩放比例 */
|
|
|
+ scale: number = 1;
|
|
|
+
|
|
|
+ /** 是否可见 */
|
|
|
+ _visible: boolean = true;
|
|
|
+ get visible(): boolean {
|
|
|
+ return this._visible;
|
|
|
+ } // Get visible
|
|
|
+ set visible(v: boolean) {
|
|
|
+ this._visible = v;
|
|
|
+ this.update();
|
|
|
+ } // Set visible
|
|
|
+
|
|
|
+ /** 是否可以移动 */
|
|
|
+ moveable: boolean = false;
|
|
|
+ /** 是否正在移动 */
|
|
|
+ private _isMoving = false;
|
|
|
+
|
|
|
+ /** 是否可用 */
|
|
|
+ enabled: boolean = true;
|
|
|
+
|
|
|
+ /** 是否可被选中 */
|
|
|
+ selectable = false;
|
|
|
+ /** 是否被选中 */
|
|
|
+ private _selected = false;
|
|
|
+ get selected(): boolean {
|
|
|
+ return this._selected && this.selectable && this.enabled;
|
|
|
+ } // Get selected
|
|
|
+ set selected(value: boolean) {
|
|
|
+ // 如果选择状态未变更
|
|
|
+ if (this.selected == value) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this._selected = value;
|
|
|
+ this.update();
|
|
|
+ } // Set selected
|
|
|
+
|
|
|
+ /** 是否进行变形 */
|
|
|
+ isTransform = true;
|
|
|
+
|
|
|
+ /** 鼠标按下时位置 */
|
|
|
+ private _mouseDownPos = new SPoint();
|
|
|
+
|
|
|
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
+ // 函数
|
|
|
+ /**
|
|
|
+ * 构造函数
|
|
|
+ *
|
|
|
+ * @param parent 指向父对象
|
|
|
+ */
|
|
|
+ constructor(parent: SGraphItem | null = null) {
|
|
|
+ super();
|
|
|
+ if (parent) {
|
|
|
+ this.parent = parent;
|
|
|
+ }
|
|
|
+ } // Function constructor()
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Item绘制框架
|
|
|
+ *
|
|
|
+ * @param painter painter对象
|
|
|
+ * @param rect 绘制区域
|
|
|
+ */
|
|
|
+ onPaint(painter: SPainter, rect: SRect): void {
|
|
|
+ this.onDraw(painter);
|
|
|
+ for (let item of this.children) {
|
|
|
+ // 如果item不可见
|
|
|
+ if (!item.visible) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ // 保存画布状态
|
|
|
+ painter.save();
|
|
|
+ // item位移到指定位置绘制
|
|
|
+ painter.translate(item.x, item.y);
|
|
|
+
|
|
|
+ // 如果不进行变形处理,则取消painter的变型操作
|
|
|
+ if (!item.isTransform) {
|
|
|
+ let matrix = painter.worldTransform;
|
|
|
+ let x0 = matrix.e;
|
|
|
+ let y0 = matrix.f;
|
|
|
+ painter.resetTransform();
|
|
|
+ painter.translate(x0, y0);
|
|
|
+ }
|
|
|
+ // 设置绘制区域
|
|
|
+ // canvas.clip(item.boundingRect())
|
|
|
+ // 绘制item
|
|
|
+ item.onPaint(painter, rect);
|
|
|
+ // 恢复画布状态
|
|
|
+ painter.restore();
|
|
|
+ }
|
|
|
+ } // Function onPaint()
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Item绘制操作
|
|
|
+ *
|
|
|
+ * @param painter painter对象
|
|
|
+ */
|
|
|
+ onDraw(painter: SPainter): void {} // Function onDraw()
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 隐藏对象
|
|
|
+ */
|
|
|
+ hide(): void {
|
|
|
+ this.visible = false;
|
|
|
+ } // Function hide()
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 显示对象
|
|
|
+ */
|
|
|
+ show(): void {
|
|
|
+ this.visible = true;
|
|
|
+ } // Function show()
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 更新Item
|
|
|
+ */
|
|
|
+ update(): void {
|
|
|
+ if (null != this.scene) {
|
|
|
+ const view = this.scene.view;
|
|
|
+ if (null != view) {
|
|
|
+ view.update();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } // Function update()
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Item对象边界区域
|
|
|
+ *
|
|
|
+ * @return 对象边界区域
|
|
|
+ */
|
|
|
+ boundingRect(): SRect {
|
|
|
+ return new SRect(0, 0, 10, 10);
|
|
|
+ } // Function boundingRect()
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 移动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 boolean
|
|
|
+ */
|
|
|
+ contains(x: number, y: number): boolean {
|
|
|
+ return this.boundingRect().contains(x - this.x, y - this.y);
|
|
|
+ } // Function contains()
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获得item的路径节点列表。(该节点被加载到场景中,如果未被加载到场景中,计算会出错)
|
|
|
+ *
|
|
|
+ * @return *[]
|
|
|
+ */
|
|
|
+ itemPath(): SGraphItem[] {
|
|
|
+ if (this.parent != null) {
|
|
|
+ let list = this.parent.itemPath();
|
|
|
+ list.push(this);
|
|
|
+ return list;
|
|
|
+ }
|
|
|
+
|
|
|
+ return [this];
|
|
|
+ } // Function itemPath()
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将场景中的xy坐标转换成item坐标。(该节点被加载到场景中,如果未被加载到场景中,计算会出错)
|
|
|
+ *
|
|
|
+ * @param x 场景中的横坐标
|
|
|
+ * @param y 场景中的纵坐标
|
|
|
+ *
|
|
|
+ * @return 在item中的坐标
|
|
|
+ */
|
|
|
+ mapFromScene(x: number, y: number): SPoint {
|
|
|
+ let list = this.itemPath();
|
|
|
+ let x0 = x;
|
|
|
+ let y0 = y;
|
|
|
+ for (let item of list) {
|
|
|
+ x0 = (x0 - item.x) / item.scale;
|
|
|
+ y0 = (y0 - item.y) / item.scale;
|
|
|
+ }
|
|
|
+
|
|
|
+ return new SPoint(x0, y0);
|
|
|
+ } // Function mapFromScene()
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将item中的xy坐标转换成场景坐标。(该节点被加载到场景中,如果未被加载到场景中,计算会出错)
|
|
|
+ *
|
|
|
+ * @param x item中的横坐标
|
|
|
+ * @param y item中的纵坐标
|
|
|
+ *
|
|
|
+ * @return 在场景中的坐标
|
|
|
+ */
|
|
|
+ mapToScene(x: number, y: number): SPoint {
|
|
|
+ if (this.parent == null) {
|
|
|
+ return new SPoint(x, y);
|
|
|
+ }
|
|
|
+
|
|
|
+ return this.parent.mapToScene(
|
|
|
+ x * this.scale + this.x,
|
|
|
+ y * this.scale + this.y
|
|
|
+ );
|
|
|
+ } // Function mapToScene()
|
|
|
+
|
|
|
+ // =================================================================================================================
|
|
|
+ // 事件
|
|
|
+ /**
|
|
|
+ * 鼠标单击事件
|
|
|
+ *
|
|
|
+ * @param event 保存事件参数
|
|
|
+ * @return boolean
|
|
|
+ */
|
|
|
+ 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(event.x, event.y) && item.onClick(ce)) {
|
|
|
+ // 如果点在子项目上且子项目处理了事件
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ } // Function onClick()
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 鼠标双击事件
|
|
|
+ *
|
|
|
+ * @param event 保存事件参数
|
|
|
+ * @return boolean
|
|
|
+ */
|
|
|
+ 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(event.x, event.y) && item.onDoubleClick(ce)) {
|
|
|
+ // 如果点在子项目上且子项目处理了事件
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ } // Function onDoubleClick()
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 鼠标按下事件
|
|
|
+ *
|
|
|
+ * @param event 保存事件参数
|
|
|
+ * @return boolean
|
|
|
+ */
|
|
|
+ onMouseDown(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(event.x, event.y) && item.onMouseDown(ce)) {
|
|
|
+ // 如果点在子项目上且子项目处理了事件
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // if (this.selectable) {
|
|
|
+ // this.clickSelect(event);
|
|
|
+ // }
|
|
|
+ if (this.moveable) {
|
|
|
+ this._mouseDownPos = new SPoint(event.x, event.y);
|
|
|
+ this._isMoving = true;
|
|
|
+ this.grabItem(this);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ } // Function onMouseDown()
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 鼠标移动事件
|
|
|
+ *
|
|
|
+ * @param event 保存事件参数
|
|
|
+ * @return boolean
|
|
|
+ */
|
|
|
+ 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(event.x, event.y) && item.onMouseMove(ce)) {
|
|
|
+ // 如果点在子项目上且子项目处理了事件
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (
|
|
|
+ event.buttons & SMouseButton.LeftButton &&
|
|
|
+ this.moveable &&
|
|
|
+ this._isMoving
|
|
|
+ ) {
|
|
|
+ let old = new SPoint(this.pos.x, this.pos.y);
|
|
|
+ this.moveTo(
|
|
|
+ this.pos.x + event.x - this._mouseDownPos.x,
|
|
|
+ this.pos.y + event.y - this._mouseDownPos.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;
|
|
|
+ } // Function onMouseMove()
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 释放鼠标事件
|
|
|
+ *
|
|
|
+ * @param event 保存事件参数
|
|
|
+ * @return boolean
|
|
|
+ */
|
|
|
+ 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(event.x, event.y) && item.onMouseUp(ce)) {
|
|
|
+ // 如果点在子项目上且子项目处理了事件
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this._isMoving = false;
|
|
|
+ this.releaseItem();
|
|
|
+ return false;
|
|
|
+ } // Function onMouseUp()
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 鼠标进入事件
|
|
|
+ *
|
|
|
+ * @param event 保存事件参数
|
|
|
+ * @return boolean
|
|
|
+ */
|
|
|
+ onMouseEnter(event: SMouseEvent): boolean {
|
|
|
+ return false;
|
|
|
+ } // Function onMouseEnter()
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 鼠标离开事件
|
|
|
+ *
|
|
|
+ * @param event 保存事件参数
|
|
|
+ * @return boolean
|
|
|
+ */
|
|
|
+ onMouseLeave(event: SMouseEvent): boolean {
|
|
|
+ return false;
|
|
|
+ } // Function onMouseLeave()
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 上下文菜单事件
|
|
|
+ *
|
|
|
+ * @param event 事件参数
|
|
|
+ */
|
|
|
+ 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(event.x, event.y) && item.onContextMenu(ce)) {
|
|
|
+ // 如果点在子项目上且子项目处理了事件
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ } // Function onContextMenu()
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按键按下事件
|
|
|
+ *
|
|
|
+ * @param event 事件参数
|
|
|
+ */
|
|
|
+ onKeyDown(event: KeyboardEvent): void {} // Function onKeyDown()
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按键press事件
|
|
|
+ *
|
|
|
+ * @param event 事件参数
|
|
|
+ */
|
|
|
+ onKeyPress(event: KeyboardEvent): void {} // Function onKeyPress()
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按键松开事件
|
|
|
+ *
|
|
|
+ * @param event 事件参数
|
|
|
+ */
|
|
|
+ onKeyUp(event: KeyboardEvent): void {} // Function onKeyUp()
|
|
|
+
|
|
|
+ // =================================================================================================================
|
|
|
+ // 私有方法
|
|
|
+ /**
|
|
|
+ * 按ZOrder排序
|
|
|
+ *
|
|
|
+ * @param a 比较元素1
|
|
|
+ * @param b 比较元素2
|
|
|
+ * @return {number}
|
|
|
+ */
|
|
|
+ private static sortItemZOrder(a: SGraphItem, b: SGraphItem): number {
|
|
|
+ return a.zOrder - b.zOrder;
|
|
|
+ } // Function sortItemZOrder()
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 鼠标事件转子对象鼠标事件
|
|
|
+ *
|
|
|
+ * @param child 子对象
|
|
|
+ * @param event 事件参数
|
|
|
+ * @return 子对象鼠标事件
|
|
|
+ */
|
|
|
+ private static toChildMouseEvent(
|
|
|
+ child: SGraphItem,
|
|
|
+ event: SMouseEvent
|
|
|
+ ): SMouseEvent {
|
|
|
+ let ce = new SMouseEvent(event);
|
|
|
+ ce.x = (event.x - child.x) / child.scale;
|
|
|
+ ce.y = (event.y - child.y) / child.scale;
|
|
|
+ return ce;
|
|
|
+ } // Function toChildMouseEvent()
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 锁定item
|
|
|
+ *
|
|
|
+ * @param item 被锁定的item
|
|
|
+ */
|
|
|
+ private grabItem(item: SGraphItem): void {
|
|
|
+ if (this.scene != null) {
|
|
|
+ this.scene.grabItem = item;
|
|
|
+ }
|
|
|
+ } // Function grabItem
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 释放被锁定的item
|
|
|
+ */
|
|
|
+ private releaseItem(): void {
|
|
|
+ if (this.scene != null) {
|
|
|
+ this.scene.grabItem = null;
|
|
|
+ }
|
|
|
+ } // Function grabItem
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断是否处理事件
|
|
|
+ *
|
|
|
+ * @return true: 处理事件,否则不处理
|
|
|
+ */
|
|
|
+ private acceptEvent(): boolean {
|
|
|
+ return this.visible && this.enabled;
|
|
|
+ } // Function acceptEvent()
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 点选item对象
|
|
|
+ *
|
|
|
+ * @param event 事件参数
|
|
|
+ */
|
|
|
+ private clickSelect(event: SMouseEvent): void {
|
|
|
+ // 如果Item不可被选中,或没有按下鼠标左键,则直接返回
|
|
|
+ if (!this.selectable) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果按下Ctrl键,只改变当前item的选择标志
|
|
|
+ if (event.ctrlKey) {
|
|
|
+ this.selected = !this.selected;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.selected = true;
|
|
|
+ } // Function clickSelect()
|
|
|
+} // Class SGraphItem
|