diff --git a/graphic-pixi b/graphic-pixi index cc73757..3e1bbc9 160000 --- a/graphic-pixi +++ b/graphic-pixi @@ -1 +1 @@ -Subproject commit cc737578baa0dadef5c2b82b6ada66c117ba3d6b +Subproject commit 3e1bbc92bf3e1604ec54db0e9188a1677ac20388 diff --git a/src/jl-graphic/app/JlDrawApp.ts b/src/jl-graphic/app/JlDrawApp.ts index 1fe6eab..25ef784 100644 --- a/src/jl-graphic/app/JlDrawApp.ts +++ b/src/jl-graphic/app/JlDrawApp.ts @@ -22,7 +22,15 @@ import { } from '../plugins'; import { CommonMouseTool } from '../plugins/CommonMousePlugin'; import { MenuItemOptions } from '../ui/Menu'; -import { DOWN, LEFT, RIGHT, UP, recursiveChildren } from '../utils'; +import { + DOWN, + DebouncedFunction, + LEFT, + RIGHT, + UP, + debounce, + recursiveChildren, +} from '../utils'; import { GraphicDataUpdateOperation, UpdateCanvasOperation, @@ -233,6 +241,16 @@ export interface IDrawApp extends IGraphicApp { * @param data */ updateGraphicAndRecord(g: JlGraphic, data: GraphicData): void; + /** + * 绑定form表单对象 + * @param form + */ + bindFormData(form: GraphicData): void; + /** + * 解绑form表单对象 + * @param form + */ + unbindFormData(form: GraphicData): void; } /** @@ -258,6 +276,8 @@ export class JlDrawApp extends GraphicApp implements IDrawApp { drawAssistants: DrawAssistant[] = []; _drawing = false; + private debouncedFormDataUpdator: DebouncedFunction<(g: JlGraphic) => void>; + get drawing(): boolean { return this._drawing; } @@ -274,6 +294,9 @@ export class JlDrawApp extends GraphicApp implements IDrawApp { this.appOperationRecord(); // 绑定通用键盘操作 this.bindKeyboardOperation(); + this.formDataSyncListen(); + + this.debouncedFormDataUpdator = debounce(this.doFormDataUpdate, 60); } setOptions(options: DrawAppOptions): void { @@ -484,6 +507,57 @@ export class JlDrawApp extends GraphicApp implements IDrawApp { graphic.eventMode = 'static'; graphic.selectable = true; graphic.draggable = true; + graphic.on('repaint', () => { + this.handleFormDataUpdate(graphic); + }); + graphic.on('transformend', () => { + this.handleFormDataUpdate(graphic); + }); + } + + formData: GraphicData | undefined = undefined; + + /** + * 绑定form表单对象 + * @param form + */ + bindFormData(form: GraphicData): void { + this.formData = form; + if (this.selectedGraphics.length == 1) { + this.formData.copyFrom(this.selectedGraphics[0].saveData()); + } + } + + /** + * 移除form绑定 + * @param form + */ + unbindFormData(form: GraphicData): void { + if (this.formData == form) { + this.formData = undefined; + } + } + + private formDataSyncListen(): void { + this.on('graphicselected', () => { + if (this.selectedGraphics.length == 1) { + this.handleFormDataUpdate(this.selectedGraphics[0]); + } + }); + } + + /** + * 处理表单数据更新(使用debounce限流) + */ + private handleFormDataUpdate(g: JlGraphic): void { + this.debouncedFormDataUpdator(this, g); + } + + private doFormDataUpdate(g: JlGraphic): void { + if (this.selectedGraphics.length > 1) return; + if (this.formData && g.type === this.formData.graphicType) { + this.formData.copyFrom(g.saveData()); + } } updateCanvasAndRecord(data: ICanvasProperties) { diff --git a/src/jl-graphic/app/JlGraphicApp.ts b/src/jl-graphic/app/JlGraphicApp.ts index b47fab1..afc67d5 100644 --- a/src/jl-graphic/app/JlGraphicApp.ts +++ b/src/jl-graphic/app/JlGraphicApp.ts @@ -46,6 +46,7 @@ import { } from '../plugins/KeyboardPlugin'; import { ContextMenu, ContextMenuPlugin } from '../ui/ContextMenu'; import { MenuItemOptions } from '../ui/Menu'; +import { DebouncedFunction, debounce } from '../utils'; import { getRectangleCenter, recursiveChildren } from '../utils/GraphicUtils'; import { GraphicCreateOperation, @@ -317,6 +318,7 @@ export interface GraphicAppEvents extends GlobalMixins.GraphicAppEvents { 'options-update': [options: GraphicAppOptions]; // 配置更新 graphicselectedchange: [graphic: JlGraphic, selected: boolean]; graphicchildselectedchange: [child: DisplayObject, selected: boolean]; + graphicselected: [graphics: JlGraphic[]]; 'viewport-scaled': [vp: Viewport]; drag_op_start: [event: AppDragEvent]; drag_op_move: [event: AppDragEvent]; @@ -509,11 +511,6 @@ export interface IGraphicScene extends EventEmitter { * @param graphics */ updateSelected(...graphics: JlGraphic[]): void; - /** - * 发布选中对象改变事件 - * @param graphic - */ - fireSelectedChange(graphic: JlGraphic): void; /** * 选中所有图形 */ @@ -563,6 +560,8 @@ abstract class GraphicSceneBase menuPlugin: ContextMenuPlugin; // 菜单插件 + private debounceEmitFunc: DebouncedFunction<() => void>; + wsMsgBroker: AppWsMsgBroker; // websocket消息代理 constructor(options: GraphicAppOptions) { super(); @@ -628,6 +627,11 @@ abstract class GraphicSceneBase this.menuPlugin = new ContextMenuPlugin(this); this.wsMsgBroker = new AppWsMsgBroker(this); + + this.debounceEmitFunc = debounce(this.doEmitAppGraphicSelected, 50); + this.on('graphicselectedchange', () => { + this.debounceEmitFunc(this); + }); } abstract get app(): GraphicApp; @@ -883,12 +887,10 @@ abstract class GraphicSceneBase // graphic可能是vue的Proxy对象,会导致canvas删除时因不是同一个对象而无法从画布移除 const g = this.graphicStore.deleteGraphics(graphic); if (g) { + // 清除选中 + g.updateSelected(false); // 从画布移除 this.canvas.removeGraphic(g); - // 清除选中 - if (g.updateSelected(false)) { - this.fireSelectedChange(g); - } // 对象删除处理 g.onDelete(); this.emit('graphicdeleted', g); @@ -936,15 +938,6 @@ abstract class GraphicSceneBase this.updateSelected(...this.queryStore.getAllGraphics()); } - /** - * 发送选中变化事件 - * @param graphic - */ - fireSelectedChange(graphic: JlGraphic) { - // console.log('通知选中变化', this.selecteds) - const select = graphic.selected; - this.emit('graphicselectedchange', graphic, select); - } /** * 更新选中 */ @@ -955,16 +948,19 @@ abstract class GraphicSceneBase } if (graphic.selected) { graphic.updateSelected(false); - this.fireSelectedChange(graphic); } }); graphics.forEach((graphic) => { - if (graphic.updateSelected(true)) { - this.fireSelectedChange(graphic); - } + graphic.updateSelected(true); }); } + private doEmitAppGraphicSelected(): void { + // 场景发布图形选中 + this.emit('graphicselected', this.selectedGraphics); + // this.app.emit('graphicselected', this.selectedGraphics); + } + /** * 更新画布 * @param param diff --git a/src/jl-graphic/core/JlGraphic.ts b/src/jl-graphic/core/JlGraphic.ts index 3891196..595bf59 100644 --- a/src/jl-graphic/core/JlGraphic.ts +++ b/src/jl-graphic/core/JlGraphic.ts @@ -642,6 +642,10 @@ export abstract class JlGraphic extends Container { this.removeAllChildSelected(); this.emit('unselected', this); } + const app = this.getGraphicApp(); + if (app) { + app.emit('graphicselectedchange', this, this.selected); + } } hasSelectedChilds(): boolean { @@ -678,11 +682,16 @@ export abstract class JlGraphic extends Container { }); } fireChildSelected(child: DisplayObject) { - if (child.selected) { + const selected = child.selected; + if (selected) { this.emit('childselected', child); } else { this.emit('childunselected', child); } + const app = this.getGraphicApp(); + if (app) { + app.emit('graphicchildselectedchange', child, selected); + } } exitChildEdit() { this.childEdit = false; diff --git a/src/jl-graphic/plugins/CommonMousePlugin.ts b/src/jl-graphic/plugins/CommonMousePlugin.ts index 76993ff..7ff07e0 100644 --- a/src/jl-graphic/plugins/CommonMousePlugin.ts +++ b/src/jl-graphic/plugins/CommonMousePlugin.ts @@ -271,7 +271,6 @@ export class CommonMouseTool extends AppInteractionPlugin { graphic.invertChildSelected(target); } else { graphic.invertSelected(); - app.fireSelectedChange(graphic); } } } else { diff --git a/src/jl-graphic/plugins/GraphicTransformPlugin.ts b/src/jl-graphic/plugins/GraphicTransformPlugin.ts index fcd259e..17b3b02 100644 --- a/src/jl-graphic/plugins/GraphicTransformPlugin.ts +++ b/src/jl-graphic/plugins/GraphicTransformPlugin.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { Container, DisplayObject, @@ -17,9 +18,11 @@ import { JlGraphic } from '../core'; import { AbsorbablePosition, VectorText } from '../graphic'; import { DraggablePoint } from '../graphic/DraggablePoint'; import { + DebouncedFunction, angleToAxisx, calculateLineMidpoint, convertRectangleToPolygonPoints, + debounce, distance, recursiveChildren, } from '../utils'; @@ -298,6 +301,8 @@ export class GraphicTransformPlugin extends InteractionPluginBase { ap.tryAbsorb(...targets); } } + // const start = new Date().getTime(); + // 事件发布 targets.forEach((target) => { if (target.shiftStartPoint && target.shiftLastPoint) { @@ -314,6 +319,8 @@ export class GraphicTransformPlugin extends InteractionPluginBase { ); } }); + // const dt = new Date().getTime() - start; + // console.log('拖拽耗时', `${dt}ms`, targets); } } } @@ -847,11 +854,14 @@ export class BoundsGraphic extends Graphics { alpha: 1, }; obj: DisplayObject; + debouncedRedraw: DebouncedFunction<() => void>; constructor(graphic: DisplayObject) { super(); this.obj = graphic; this.name = BoundsGraphic.Name; this.visible = false; + + this.debouncedRedraw = debounce(this.doRedraw, 50); this.obj.on('transformstart', this.onObjTransformStart, this); this.obj.on('transformend', this.onObjTransformEnd, this); if (this.obj.children && this.obj.children.length > 0) { @@ -880,7 +890,6 @@ export class BoundsGraphic extends Graphics { onGraphicRepaint(): void { if (this.visible) { this.redraw(); - this.visible = true; } } @@ -892,8 +901,13 @@ export class BoundsGraphic extends Graphics { } redraw() { + this.debouncedRedraw(this); + } + doRedraw() { + const visible = this.visible; this.visible = false; // 屏蔽包围框本身 const bounds = new Polygon(this.obj.localBoundsToCanvasPoints()); this.clear().lineStyle(BoundsGraphic.BoundsLineStyle).drawShape(bounds); + this.visible = visible; } } diff --git a/src/jl-graphic/utils/debounce.ts b/src/jl-graphic/utils/debounce.ts new file mode 100644 index 0000000..27310c7 --- /dev/null +++ b/src/jl-graphic/utils/debounce.ts @@ -0,0 +1,37 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +export interface DebouncedFunction any> { + (context: ThisParameterType, ...args: Parameters): void; + cancel: () => void; +} + +export function debounce) => any>( + fn: F, + waitMs = 250 +): DebouncedFunction { + let timeoutId: ReturnType | undefined; + + const debouncedFunction = function ( + context: ThisParameterType, + ...args: Parameters + ) { + const invokeFunction = function () { + timeoutId = undefined; + fn.apply(context, args); + }; + + if (timeoutId !== undefined) { + console.debug('debounce clear timeout', fn); + clearTimeout(timeoutId); + } + + timeoutId = setTimeout(invokeFunction, waitMs); + }; + + debouncedFunction.cancel = function () { + if (timeoutId !== undefined) { + clearTimeout(timeoutId); + } + }; + + return debouncedFunction; +} diff --git a/src/jl-graphic/utils/index.ts b/src/jl-graphic/utils/index.ts index 57ec464..f4c9aa6 100644 --- a/src/jl-graphic/utils/index.ts +++ b/src/jl-graphic/utils/index.ts @@ -3,6 +3,8 @@ import { Point, Rectangle } from 'pixi.js'; export * from './GraphicUtils'; export * from './IntersectUtils'; +export * from './debounce'; + export const UP: Point = new Point(0, -1); export const DOWN: Point = new Point(0, 1); export const LEFT: Point = new Point(-1, 0); diff --git a/src/stores/draw-store.ts b/src/stores/draw-store.ts index bfec508..6576df7 100644 --- a/src/stores/draw-store.ts +++ b/src/stores/draw-store.ts @@ -11,6 +11,7 @@ import { initJkDrawApp, } from 'src/drawApp/jkApp'; import { DrawAssistant, IDrawApp, IJlCanvas, JlGraphic } from 'src/jl-graphic'; +import { markRaw } from 'vue'; export const useDrawStore = defineStore('draw', { state: () => ({ @@ -101,8 +102,8 @@ export const useDrawStore = defineStore('draw', { } } }); - app.on('graphicselectedchange', () => { - this.selectedGraphics = (app as IDrawApp).selectedGraphics; + app.on('graphicselected', (graphics) => { + this.selectedGraphics = markRaw(graphics); }); this.selectedGraphics = []; return app;