添加debounce函数实现

drawApp添加绑定/取消绑定表单对象(处理表单更新覆盖图形变换等操作)
调整图形选中事件发布逻辑和触发总的图形选中事件发布
This commit is contained in:
walker 2023-09-18 11:15:40 +08:00
parent cc737578ba
commit 3e1bbc92bf
10 changed files with 209 additions and 49 deletions

View File

@ -5,7 +5,7 @@
outlined
v-model.number="linkModel.lineWidth"
type="number"
@blur="onUpdate"
@blur="onFormUpdate"
label="线宽"
lazy-rules
:rules="[(val) => (val && val > 0) || '画布宽必须大于0']"
@ -14,7 +14,7 @@
<q-input
outlined
v-model="linkModel.lineColor"
@blur="onUpdate"
@blur="onFormUpdate"
label="线色"
lazy-rules
:rules="[(val) => (val && val.length > 0) || '线色不能为空']"
@ -27,7 +27,7 @@
@change="
(val) => {
linkModel.lineColor = val;
onUpdate();
onFormUpdate();
}
"
/>
@ -50,7 +50,7 @@
outlined
v-model.number="linkModel.segmentsCount"
type="number"
@blur="onUpdate"
@blur="onFormUpdate"
label="曲线分段数量"
lazy-rules
:rules="[(val) => (val && val > 0) || '曲线分段数量必须大于0']"
@ -62,34 +62,41 @@
import { LinkData } from 'src/examples/app/graphics/LinkInteraction';
import { Link } from 'src/graphics/link/Link';
import { useDrawStore } from 'src/stores/draw-store';
import { onMounted, reactive, watch } from 'vue';
import { onMounted, onUnmounted, reactive, watch } from 'vue';
const drawStore = useDrawStore();
const linkModel = reactive(new LinkData());
drawStore.$subscribe;
watch(
() => drawStore.selectedGraphic,
(val) => {
if (val && val.type == Link.Type) {
// watch(
// () => drawStore.selectedGraphic,
// (val) => {
// console.log('');
// if (val && val.type == Link.Type) {
// console.log('link');
linkModel.copyFrom(val.saveData() as LinkData);
}
}
);
// linkModel.copyFrom(val.saveData() as LinkData);
// }
// }
// );
onMounted(() => {
// console.log('link mounted');
const link = drawStore.selectedGraphic as Link;
if (link) {
linkModel.copyFrom(link.saveData());
}
drawStore.bindFormData(linkModel);
// const link = drawStore.selectedGraphic as Link;
// if (link) {
// linkModel.copyFrom(link.saveData());
// }
});
function onUpdate() {
console.log('link 属性更新');
onUnmounted(() => {
console.log('link 属性表单 unmounted');
drawStore.unbindFormData(linkModel);
});
function onFormUpdate() {
const link = drawStore.selectedGraphic as Link;
if (link) {
console.log('link 属性更新', link);
drawStore.getDrawApp().updateGraphicAndRecord(link, linkModel);
}
}

View File

@ -15,7 +15,7 @@
import { SignalData } from 'src/examples/app/graphics/SignalInteraction';
import { Signal } from 'src/graphics/signal/Signal';
import { useDrawStore } from 'src/stores/draw-store';
import { onMounted, reactive, ref, watch } from 'vue';
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
const drawStore = useDrawStore();
const signalModel = reactive(new SignalData());
@ -40,6 +40,7 @@ watch(
);
onMounted(() => {
drawStore.bindFormData(signalModel);
const signal = drawStore.selectedGraphic as Signal;
if (signal) {
signalModel.copyFrom(signal.saveData());
@ -47,6 +48,10 @@ onMounted(() => {
}
});
onUnmounted(() => {
drawStore.unbindFormData(signalModel);
});
function onUpdate() {
signalModel.direction = (directionSelect as never)[signalDirection.value];
const signal = drawStore.selectedGraphic as Signal;

View File

@ -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) {

View File

@ -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<GraphicAppEvents> {
* @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

View File

@ -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;

View File

@ -271,7 +271,6 @@ export class CommonMouseTool extends AppInteractionPlugin {
graphic.invertChildSelected(target);
} else {
graphic.invertSelected();
app.fireSelectedChange(graphic);
}
}
} else {

View File

@ -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;
}
}

View File

@ -0,0 +1,37 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export interface DebouncedFunction<F extends (...args: any[]) => any> {
(context: ThisParameterType<F>, ...args: Parameters<F>): void;
cancel: () => void;
}
export function debounce<F extends (...args: Parameters<F>) => any>(
fn: F,
waitMs = 250
): DebouncedFunction<F> {
let timeoutId: ReturnType<typeof setTimeout> | undefined;
const debouncedFunction = function (
context: ThisParameterType<F>,
...args: Parameters<F>
) {
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;
}

View File

@ -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);

View File

@ -1,6 +1,6 @@
import { defineStore } from 'pinia';
import { destroyDrawApp, getDrawApp, initDrawApp } from 'src/examples/app';
import { DrawAssistant, IDrawApp, JlGraphic } from 'src/jlgraphic';
import { DrawAssistant, GraphicData, IDrawApp, JlGraphic } from 'src/jlgraphic';
import { IJlCanvas } from 'src/jlgraphic/app/JlGraphicApp';
export const useDrawStore = defineStore('draw', {
@ -52,6 +52,22 @@ export const useDrawStore = defineStore('draw', {
getJlCanvas(): IJlCanvas {
return this.getDrawApp().canvas;
},
bindFormData(form: GraphicData): void {
console.log('绑定form数据', form);
const app = this.getDrawApp();
app.bindFormData(form);
// app.app.on('graphicselectedchange', () => {
// if (app.selectedGraphics.length == 1) {
// const g = app.selectedGraphics[0];
// if (g.type === form.graphicType) {
// form.copyFrom(g.saveData());
// }
// }
// });
},
unbindFormData(form: GraphicData): void {
console.log('取消绑定form数据', form);
},
initDrawApp() {
const app = initDrawApp();
app.on('interaction-plugin-resume', (plugin) => {
@ -63,7 +79,8 @@ export const useDrawStore = defineStore('draw', {
}
}
});
app.on('graphicselectedchange', () => {
app.on('graphicselected', () => {
console.log('批量选中事件', app.selectedGraphics.length);
this.selectedGraphics = app.selectedGraphics;
});
this.selectedGraphics = [];