SGraphItem.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  1. import { SMouseButton, SMouseEvent, SObject } from "@saga-web/base/lib";
  2. import { SPainter, SPoint, SRect } from "@saga-web/draw/lib";
  3. import { SGraphScene } from "./SGraphScene";
  4. /**
  5. * Graph图形引擎Item类
  6. *
  7. * @author 庞利祥(sybotan@126.com)
  8. */
  9. export class SGraphItem extends SObject {
  10. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  11. // 属性
  12. /** 场景对象 */
  13. private _scene: SGraphScene | null = null;
  14. get scene(): SGraphScene | null {
  15. if (null != this._parent) {
  16. return this._parent.scene;
  17. } else {
  18. return this._scene;
  19. }
  20. } // Get scene
  21. set scene(v: SGraphScene | null) {
  22. this._scene = v;
  23. this.update();
  24. } // Set scene
  25. /** parent属性存值函数 */
  26. private _parent: SGraphItem | null = null;
  27. get parent(): SGraphItem | null {
  28. return this._parent;
  29. } // Get parent
  30. set parent(v: SGraphItem | null) {
  31. // 如果parent未变更
  32. if (this.parent == v) {
  33. return;
  34. }
  35. // 如果原parent不为空
  36. if (this._parent != null) {
  37. // 将节点从原parent节点中摘除
  38. let i = this._parent.children.indexOf(this);
  39. this._parent.children.splice(i, 1);
  40. }
  41. this._parent = v;
  42. this.update();
  43. // 如果新parent不为空
  44. if (this._parent != null) {
  45. // 将节点加入到新parent节点中
  46. this._parent.children.push(this);
  47. this._parent.children.sort(SGraphItem.sortItemZOrder);
  48. }
  49. } // Set parent()
  50. /** 子节点 */
  51. children: SGraphItem[] = [];
  52. /** Z轴顺序 */
  53. private _zOrder: number = 0;
  54. get zOrder(): number {
  55. return this._zOrder;
  56. } // Get zOrder
  57. set zOrder(v: number) {
  58. this._zOrder = v;
  59. if (this._parent != null) {
  60. this._parent.children.sort(SGraphItem.sortItemZOrder);
  61. }
  62. this.update();
  63. } // Set zOrder
  64. /** 位置 */
  65. pos: SPoint = new SPoint(0, 0);
  66. /** X轴坐标 */
  67. get x(): number {
  68. return this.pos.x;
  69. } // Get x
  70. set x(v: number) {
  71. if (this.pos.x == v) {
  72. return;
  73. }
  74. let old = this.pos.x;
  75. this.pos.x = v;
  76. this.update();
  77. } // Set x
  78. /** Y轴坐标 */
  79. get y(): number {
  80. return this.pos.y;
  81. } // Get y
  82. set y(v: number) {
  83. if (this.pos.y == v) {
  84. return;
  85. }
  86. let old = this.pos.y;
  87. this.pos.y = v;
  88. this.update();
  89. } // Set y
  90. /** 缩放比例 */
  91. scale: number = 1;
  92. /** 是否可见 */
  93. _visible: boolean = true;
  94. get visible(): boolean {
  95. return this._visible;
  96. } // Get visible
  97. set visible(v: boolean) {
  98. this._visible = v;
  99. this.update();
  100. } // Set visible
  101. /** 是否可以移动 */
  102. moveable: boolean = false;
  103. /** 是否正在移动 */
  104. private _isMoving = false;
  105. /** 是否可用 */
  106. enabled: boolean = true;
  107. /** 是否可被选中 */
  108. selectable = false;
  109. /** 是否被选中 */
  110. private _selected = false;
  111. get selected(): boolean {
  112. return this._selected && this.selectable && this.enabled;
  113. } // Get selected
  114. set selected(value: boolean) {
  115. // 如果选择状态未变更
  116. if (this.selected == value) {
  117. return;
  118. }
  119. this._selected = value;
  120. this.update();
  121. } // Set selected
  122. /** 是否进行变形 */
  123. isTransform = true;
  124. /** 鼠标按下时位置 */
  125. private _mouseDownPos = new SPoint();
  126. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  127. // 函数
  128. /**
  129. * 构造函数
  130. *
  131. * @param parent 指向父对象
  132. */
  133. constructor(parent: SGraphItem | null = null) {
  134. super();
  135. if (parent) {
  136. this.parent = parent;
  137. }
  138. } // Function constructor()
  139. /**
  140. * Item绘制框架
  141. *
  142. * @param painter painter对象
  143. * @param rect 绘制区域
  144. */
  145. onPaint(painter: SPainter, rect: SRect): void {
  146. this.onDraw(painter);
  147. for (let item of this.children) {
  148. // 如果item不可见
  149. if (!item.visible) {
  150. continue;
  151. }
  152. // item所占矩阵与要刷新的矩阵相交则刷新,否则不刷
  153. // if (item.boundingRect()--rect){
  154. //
  155. // }
  156. // 保存画布状态
  157. painter.save();
  158. // item位移到指定位置绘制
  159. painter.translate(item.x, item.y);
  160. // 如果不进行变形处理,则取消painter的变型操作
  161. if (!item.isTransform) {
  162. let matrix = painter.worldTransform;
  163. let x0 = matrix.e;
  164. let y0 = matrix.f;
  165. painter.resetTransform();
  166. painter.translate(x0, y0);
  167. }
  168. // 设置绘制区域
  169. // canvas.clip(item.boundingRect())
  170. // rect 调整 宽度高度取最小值 根据pos位移
  171. // 绘制item
  172. item.onPaint(painter, rect);
  173. // 恢复画布状态
  174. painter.restore();
  175. }
  176. } // Function onPaint()
  177. /**
  178. * Item绘制操作
  179. *
  180. * @param painter painter对象
  181. */
  182. onDraw(painter: SPainter): void {} // Function onDraw()
  183. /**
  184. * 隐藏对象
  185. */
  186. hide(): void {
  187. this.visible = false;
  188. } // Function hide()
  189. /**
  190. * 显示对象
  191. */
  192. show(): void {
  193. this.visible = true;
  194. } // Function show()
  195. /**
  196. * 更新Item
  197. */
  198. update(): void {
  199. if (null != this.scene) {
  200. const view = this.scene.view;
  201. if (null != view) {
  202. view.update();
  203. }
  204. }
  205. } // Function update()
  206. /**
  207. * Item对象边界区域
  208. *
  209. * @return 对象边界区域
  210. */
  211. boundingRect(): SRect {
  212. return new SRect(0, 0, 10, 10);
  213. } // Function boundingRect()
  214. /**
  215. * 移动item到指定位置
  216. *
  217. * @param x 新位置的x坐标
  218. * @param y 新位置的y坐标
  219. */
  220. moveTo(x: number, y: number): void {
  221. this.x = x;
  222. this.y = y;
  223. } // moveTo()
  224. /**
  225. * 判断item是否包含点x,y
  226. *
  227. * @param x 横坐标(当前item)
  228. * @param y 纵坐标(当前item)
  229. *
  230. * @return boolean
  231. */
  232. contains(x: number, y: number): boolean {
  233. return this.boundingRect().contains(x - this.x, y - this.y);
  234. } // Function contains()
  235. /**
  236. * 获得item的路径节点列表。(该节点被加载到场景中,如果未被加载到场景中,计算会出错)
  237. *
  238. * @return *[]
  239. */
  240. itemPath(): SGraphItem[] {
  241. if (this.parent != null) {
  242. let list = this.parent.itemPath();
  243. list.push(this);
  244. return list;
  245. }
  246. return [this];
  247. } // Function itemPath()
  248. /**
  249. * 将场景中的xy坐标转换成item坐标。(该节点被加载到场景中,如果未被加载到场景中,计算会出错)
  250. *
  251. * @param x 场景中的横坐标
  252. * @param y 场景中的纵坐标
  253. *
  254. * @return 在item中的坐标
  255. */
  256. mapFromScene(x: number, y: number): SPoint {
  257. let list = this.itemPath();
  258. let x0 = x;
  259. let y0 = y;
  260. for (let item of list) {
  261. x0 = (x0 - item.x) / item.scale;
  262. y0 = (y0 - item.y) / item.scale;
  263. }
  264. return new SPoint(x0, y0);
  265. } // Function mapFromScene()
  266. /**
  267. * 将item中的xy坐标转换成场景坐标。(该节点被加载到场景中,如果未被加载到场景中,计算会出错)
  268. *
  269. * @param x item中的横坐标
  270. * @param y item中的纵坐标
  271. *
  272. * @return 在场景中的坐标
  273. */
  274. mapToScene(x: number, y: number): SPoint {
  275. if (this.parent == null) {
  276. return new SPoint(x, y);
  277. }
  278. return this.parent.mapToScene(
  279. x * this.scale + this.x,
  280. y * this.scale + this.y
  281. );
  282. } // Function mapToScene()
  283. // =================================================================================================================
  284. // 事件
  285. /**
  286. * 鼠标单击事件
  287. *
  288. * @param event 保存事件参数
  289. * @return boolean
  290. */
  291. onClick(event: SMouseEvent): boolean {
  292. for (let i = this.children.length - 1; i >= 0; i--) {
  293. let item = this.children[i];
  294. if (!this.acceptEvent() || !item.visible) {
  295. continue;
  296. }
  297. let ce = SGraphItem.toChildMouseEvent(item, event);
  298. if (item.contains(event.x, event.y) && item.onClick(ce)) {
  299. // 如果点在子项目上且子项目处理了事件
  300. return true;
  301. }
  302. }
  303. return false;
  304. } // Function onClick()
  305. /**
  306. * 鼠标双击事件
  307. *
  308. * @param event 保存事件参数
  309. * @return boolean
  310. */
  311. onDoubleClick(event: SMouseEvent): boolean {
  312. for (let i = this.children.length - 1; i >= 0; i--) {
  313. let item = this.children[i];
  314. if (!this.acceptEvent() || !item.visible) {
  315. continue;
  316. }
  317. let ce = SGraphItem.toChildMouseEvent(item, event);
  318. if (item.contains(event.x, event.y) && item.onDoubleClick(ce)) {
  319. // 如果点在子项目上且子项目处理了事件
  320. return true;
  321. }
  322. }
  323. return false;
  324. } // Function onDoubleClick()
  325. /**
  326. * 鼠标按下事件
  327. *
  328. * @param event 保存事件参数
  329. * @return boolean
  330. */
  331. onMouseDown(event: SMouseEvent): boolean {
  332. for (let i = this.children.length - 1; i >= 0; i--) {
  333. let item = this.children[i];
  334. if (!this.acceptEvent() || !item.visible) {
  335. continue;
  336. }
  337. let ce = SGraphItem.toChildMouseEvent(item, event);
  338. if (item.contains(event.x, event.y) && item.onMouseDown(ce)) {
  339. // 如果点在子项目上且子项目处理了事件
  340. return true;
  341. }
  342. }
  343. // 选择
  344. let select = false;
  345. if (this.selectable) {
  346. select = this.clickSelect(event);
  347. }
  348. // 移动
  349. if (this.moveable) {
  350. this._mouseDownPos = new SPoint(event.x, event.y);
  351. this._isMoving = true;
  352. this.grabItem(this);
  353. return true;
  354. }
  355. return select;
  356. } // Function onMouseDown()
  357. /**
  358. * 鼠标移动事件
  359. *
  360. * @param event 保存事件参数
  361. * @return boolean
  362. */
  363. onMouseMove(event: SMouseEvent): boolean {
  364. for (let i = this.children.length - 1; i >= 0; i--) {
  365. let item = this.children[i];
  366. if (!this.acceptEvent() || !item.visible) {
  367. continue;
  368. }
  369. let ce = SGraphItem.toChildMouseEvent(item, event);
  370. if (item.contains(event.x, event.y) && item.onMouseMove(ce)) {
  371. // 如果点在子项目上且子项目处理了事件
  372. return true;
  373. }
  374. }
  375. if (
  376. event.buttons & SMouseButton.LeftButton &&
  377. this.moveable &&
  378. this._isMoving
  379. ) {
  380. let old = new SPoint(this.pos.x, this.pos.y);
  381. this.moveTo(
  382. this.pos.x + event.x - this._mouseDownPos.x,
  383. this.pos.y + event.y - this._mouseDownPos.y
  384. );
  385. this.$emit("onMove", old, this.pos);
  386. }
  387. // 处理hover
  388. const scene = this.scene;
  389. if (null != scene) {
  390. if (scene.hoverItem == null || scene.hoverItem !== this) {
  391. if (scene.hoverItem != null) {
  392. scene.hoverItem.onMouseLeave(event);
  393. }
  394. this.onMouseEnter(event);
  395. scene.hoverItem = this;
  396. }
  397. }
  398. return true;
  399. } // Function onMouseMove()
  400. /**
  401. * 释放鼠标事件
  402. *
  403. * @param event 保存事件参数
  404. * @return boolean
  405. */
  406. onMouseUp(event: SMouseEvent): boolean {
  407. for (let i = this.children.length - 1; i >= 0; i--) {
  408. let item = this.children[i];
  409. if (!this.acceptEvent() || !item.visible) {
  410. continue;
  411. }
  412. let ce = SGraphItem.toChildMouseEvent(item, event);
  413. if (item.contains(event.x, event.y) && item.onMouseUp(ce)) {
  414. // 如果点在子项目上且子项目处理了事件
  415. return true;
  416. }
  417. }
  418. this._isMoving = false;
  419. this.releaseItem();
  420. return false;
  421. } // Function onMouseUp()
  422. /**
  423. * 鼠标进入事件
  424. *
  425. * @param event 保存事件参数
  426. * @return boolean
  427. */
  428. onMouseEnter(event: SMouseEvent): boolean {
  429. return false;
  430. } // Function onMouseEnter()
  431. /**
  432. * 鼠标离开事件
  433. *
  434. * @param event 保存事件参数
  435. * @return boolean
  436. */
  437. onMouseLeave(event: SMouseEvent): boolean {
  438. return false;
  439. } // Function onMouseLeave()
  440. /**
  441. * 上下文菜单事件
  442. *
  443. * @param event 事件参数
  444. */
  445. onContextMenu(event: SMouseEvent): boolean {
  446. for (let i = this.children.length - 1; i >= 0; i--) {
  447. let item = this.children[i];
  448. if (!this.acceptEvent() || !item.visible) {
  449. continue;
  450. }
  451. let ce = SGraphItem.toChildMouseEvent(item, event);
  452. if (item.contains(event.x, event.y) && item.onContextMenu(ce)) {
  453. // 如果点在子项目上且子项目处理了事件
  454. return true;
  455. }
  456. }
  457. return false;
  458. } // Function onContextMenu()
  459. /**
  460. * 按键按下事件
  461. *
  462. * @param event 事件参数
  463. */
  464. onKeyDown(event: KeyboardEvent): void {} // Function onKeyDown()
  465. /**
  466. * 按键press事件
  467. *
  468. * @param event 事件参数
  469. */
  470. onKeyPress(event: KeyboardEvent): void {} // Function onKeyPress()
  471. /**
  472. * 按键松开事件
  473. *
  474. * @param event 事件参数
  475. */
  476. onKeyUp(event: KeyboardEvent): void {} // Function onKeyUp()
  477. /**
  478. * 取消操作item事件
  479. *
  480. * */
  481. cancelOperate(): void {} // Function cancelOperate()
  482. /**
  483. * 返回item对应的数据对象
  484. *
  485. * */
  486. toData(): any | null {
  487. return null;
  488. } // Function toData()
  489. // =================================================================================================================
  490. // 私有方法
  491. /**
  492. * 按ZOrder排序
  493. *
  494. * @param a 比较元素1
  495. * @param b 比较元素2
  496. * @return {number}
  497. */
  498. private static sortItemZOrder(a: SGraphItem, b: SGraphItem): number {
  499. return a.zOrder - b.zOrder;
  500. } // Function sortItemZOrder()
  501. /**
  502. * 鼠标事件转子对象鼠标事件
  503. *
  504. * @param child 子对象
  505. * @param event 事件参数
  506. * @return 子对象鼠标事件
  507. */
  508. private static toChildMouseEvent(
  509. child: SGraphItem,
  510. event: SMouseEvent
  511. ): SMouseEvent {
  512. let ce = new SMouseEvent(event);
  513. ce.x = (event.x - child.x) / child.scale;
  514. ce.y = (event.y - child.y) / child.scale;
  515. return ce;
  516. } // Function toChildMouseEvent()
  517. /**
  518. * 锁定item
  519. *
  520. * @param item 被锁定的item
  521. */
  522. protected grabItem(item: SGraphItem): void {
  523. if (this.scene != null) {
  524. this.scene.grabItem = item;
  525. }
  526. } // Function grabItem
  527. /**
  528. * 释放被锁定的item
  529. */
  530. protected releaseItem(): void {
  531. if (this.scene != null) {
  532. this.scene.grabItem = null;
  533. }
  534. } // Function grabItem
  535. /**
  536. * 判断是否处理事件
  537. *
  538. * @return true: 处理事件,否则不处理
  539. */
  540. private acceptEvent(): boolean {
  541. return this.visible && this.enabled;
  542. } // Function acceptEvent()
  543. /**
  544. * 点选item对象
  545. *
  546. * @param event 事件参数
  547. */
  548. private clickSelect(event: SMouseEvent): boolean {
  549. // 如果Item不可被选中,或没有按下鼠标左键,则直接返回
  550. if (!this.selectable || event.buttons != SMouseButton.LeftButton) {
  551. return false;
  552. }
  553. const scene = this.scene;
  554. if (scene == null) {
  555. return false;
  556. }
  557. // 如果按下Ctrl键,只改变当前item的选择标志
  558. if (event.ctrlKey) {
  559. scene.selectContainer.toggleItem(this);
  560. } else {
  561. scene.selectContainer.setItem(this);
  562. }
  563. return true;
  564. } // Function clickSelect()
  565. } // Class SGraphItem