graphics框架更新

This commit is contained in:
Yuan 2023-08-31 15:58:15 +08:00
parent 97fa9307b7
commit f980c4f557
19 changed files with 890 additions and 562 deletions

@ -1 +1 @@
Subproject commit 6dde86d63f08cefdea5d6532cf95e8e83f0dd0dc Subproject commit 7e9c9eb31176294726103306dcca61b80b896cf7

View File

@ -11,7 +11,6 @@ import {
} from 'pixi.js'; } from 'pixi.js';
import { GraphicIdGenerator } from '../core/IdGenerator'; import { GraphicIdGenerator } from '../core/IdGenerator';
import { GraphicData, GraphicTemplate, JlGraphic } from '../core/JlGraphic'; import { GraphicData, GraphicTemplate, JlGraphic } from '../core/JlGraphic';
import { JlOperation } from '../operation/JlOperation';
import { import {
AppDragEvent, AppDragEvent,
AppInteractionPlugin, AppInteractionPlugin,
@ -24,11 +23,16 @@ import {
import { CommonMouseTool } from '../plugins/CommonMousePlugin'; import { CommonMouseTool } from '../plugins/CommonMousePlugin';
import { MenuItemOptions } from '../ui/Menu'; import { MenuItemOptions } from '../ui/Menu';
import { DOWN, LEFT, RIGHT, UP, recursiveChildren } from '../utils'; import { DOWN, LEFT, RIGHT, UP, recursiveChildren } from '../utils';
import {
GraphicDataUpdateOperation,
UpdateCanvasOperation,
} from './BasicOperation';
import { import {
GraphicApp, GraphicApp,
GraphicAppOptions, GraphicAppOptions,
ICanvasProperties, ICanvasProperties,
JlCanvas, IGraphicApp,
IJlCanvas,
} from './JlGraphicApp'; } from './JlGraphicApp';
/** /**
@ -39,7 +43,7 @@ export abstract class GraphicDrawAssistant<
GD extends GraphicData GD extends GraphicData
> extends AppInteractionPlugin { > extends AppInteractionPlugin {
readonly __GraphicDrawAssistant = true; readonly __GraphicDrawAssistant = true;
app: JlDrawApp; app: IDrawApp;
type: string; // 图形对象类型 type: string; // 图形对象类型
description: string; // 描述 description: string; // 描述
icon: string; // 界面显示的图标 icon: string; // 界面显示的图标
@ -58,7 +62,7 @@ export abstract class GraphicDrawAssistant<
} }
constructor( constructor(
graphicApp: JlDrawApp, graphicApp: IDrawApp,
graphicTemplate: GT, graphicTemplate: GT,
icon: string, icon: string,
description: string description: string
@ -72,12 +76,12 @@ export abstract class GraphicDrawAssistant<
this.app.registerGraphicTemplates(this.graphicTemplate); this.app.registerGraphicTemplates(this.graphicTemplate);
} }
public get canvas(): JlCanvas { public get canvas(): IJlCanvas {
return this.app.canvas; return this.app.canvas;
} }
bind(): void { bind(): void {
this.app._drawing = true; this.app.drawing = true;
const canvas = this.canvas; const canvas = this.canvas;
canvas.addChild(this.container); canvas.addChild(this.container);
canvas.on('mousedown', this.onLeftDown, this); canvas.on('mousedown', this.onLeftDown, this);
@ -113,7 +117,7 @@ export abstract class GraphicDrawAssistant<
this.app.removeKeyboardListener(this.escListener); this.app.removeKeyboardListener(this.escListener);
this.app.viewport.plugins.remove('drag'); this.app.viewport.plugins.remove('drag');
this.app._drawing = false; this.app.drawing = false;
} }
onLeftDown(e: FederatedMouseEvent) {} onLeftDown(e: FederatedMouseEvent) {}
@ -152,9 +156,7 @@ export abstract class GraphicDrawAssistant<
* *
*/ */
storeGraphic(...graphics: JlGraphic[]): void { storeGraphic(...graphics: JlGraphic[]): void {
this.app.addGraphics(...graphics); this.app.addGraphicAndRecord(...graphics);
// 创建图形对象操作记录
this.app.opRecord.record(new GraphicCreateOperation(this.app, graphics));
} }
/** /**
* App * App
@ -189,21 +191,54 @@ export abstract class GraphicDrawAssistant<
} }
} }
/**
*
*/
export type DrawAssistant = GraphicDrawAssistant<GraphicTemplate, GraphicData>; export type DrawAssistant = GraphicDrawAssistant<GraphicTemplate, GraphicData>;
export interface IDrawAppOptions { /**
/** *
* */
*/ export type DrawAppOptions = GraphicAppOptions;
drawAssistants: DrawAssistant[];
}
export type DrawAppOptions = GraphicAppOptions & IDrawAppOptions; /**
*
*/
export interface IDrawApp extends IGraphicApp {
/**
*
*/
get drawing(): boolean;
/**
*
*/
set drawing(value: boolean);
/**
*
* @param options
*/
setOptions(options: DrawAppOptions): void;
/**
*
*/
getDrawAssistant<DA extends DrawAssistant>(graphicType: string): DA;
/**
*
* @param data
*/
updateCanvasAndRecord(data: ICanvasProperties): void;
/**
*
* @param g
* @param data
*/
updateGraphicAndRecord(g: JlGraphic, data: GraphicData): void;
}
/** /**
* *
*/ */
export class JlDrawApp extends GraphicApp { export class JlDrawApp extends GraphicApp implements IDrawApp {
font: BitmapFont = BitmapFont.from( font: BitmapFont = BitmapFont.from(
'coordinates', 'coordinates',
{ {
@ -223,8 +258,15 @@ export class JlDrawApp extends GraphicApp {
drawAssistants: DrawAssistant[] = []; drawAssistants: DrawAssistant[] = [];
_drawing = false; _drawing = false;
constructor(dom: HTMLElement) { get drawing(): boolean {
super(dom); return this._drawing;
}
set drawing(value: boolean) {
this._drawing = value;
}
constructor(options: DrawAppOptions) {
super(options);
this.appendDrawStatesDisplay(); this.appendDrawStatesDisplay();
@ -236,7 +278,6 @@ export class JlDrawApp extends GraphicApp {
setOptions(options: DrawAppOptions): void { setOptions(options: DrawAppOptions): void {
super.setOptions(options); super.setOptions(options);
// this.registerInteractionPlugin(...options.drawAssistants);
} }
registerInteractionPlugin(...plugins: InteractionPlugin[]): void { registerInteractionPlugin(...plugins: InteractionPlugin[]): void {
@ -310,8 +351,8 @@ export class JlDrawApp extends GraphicApp {
* *
*/ */
private appendDrawStatesDisplay(): void { private appendDrawStatesDisplay(): void {
this.app.stage.addChild(this.coordinates); this.pixi.stage.addChild(this.coordinates);
this.app.stage.addChild(this.scaleText); this.pixi.stage.addChild(this.scaleText);
const bound = this.coordinates.getLocalBounds(); const bound = this.coordinates.getLocalBounds();
this.scaleText.position.set(bound.width + 10, 0); this.scaleText.position.set(bound.width + 10, 0);
this.canvas.on('mousemove', (e) => { this.canvas.on('mousemove', (e) => {
@ -336,7 +377,7 @@ export class JlDrawApp extends GraphicApp {
new KeyListener({ new KeyListener({
value: 'KeyA', value: 'KeyA',
combinations: [CombinationKey.Ctrl], combinations: [CombinationKey.Ctrl],
onPress: (e: KeyboardEvent, app: GraphicApp) => { onPress: (e: KeyboardEvent, app: IGraphicApp) => {
if (e.ctrlKey) { if (e.ctrlKey) {
(app as JlDrawApp).selectAllGraphics(); (app as JlDrawApp).selectAllGraphics();
} }
@ -349,8 +390,8 @@ export class JlDrawApp extends GraphicApp {
new KeyListener({ new KeyListener({
value: 'KeyD', value: 'KeyD',
combinations: [CombinationKey.Shift], combinations: [CombinationKey.Shift],
onPress: (e: KeyboardEvent, app: GraphicApp) => { onPress: (e: KeyboardEvent, app: IGraphicApp) => {
app.graphicCopyPlugin.init(); this.graphicCopyPlugin.init();
}, },
}) })
); );
@ -360,7 +401,7 @@ export class JlDrawApp extends GraphicApp {
value: 'KeyZ', value: 'KeyZ',
global: true, global: true,
combinations: [CombinationKey.Ctrl], combinations: [CombinationKey.Ctrl],
onPress: (e: KeyboardEvent, app: GraphicApp) => { onPress: (e: KeyboardEvent, app: IGraphicApp) => {
app.opRecord.undo(); app.opRecord.undo();
}, },
}) })
@ -371,7 +412,7 @@ export class JlDrawApp extends GraphicApp {
value: 'KeyZ', value: 'KeyZ',
global: true, global: true,
combinations: [CombinationKey.Ctrl, CombinationKey.Shift], combinations: [CombinationKey.Ctrl, CombinationKey.Shift],
onPress: (e: KeyboardEvent, app: GraphicApp) => { onPress: (e: KeyboardEvent, app: IGraphicApp) => {
app.opRecord.redo(); app.opRecord.redo();
}, },
}) })
@ -380,8 +421,8 @@ export class JlDrawApp extends GraphicApp {
this.addKeyboardListener( this.addKeyboardListener(
new KeyListener({ new KeyListener({
value: 'Delete', value: 'Delete',
onPress: (e: KeyboardEvent, app: GraphicApp) => { onPress: (e: KeyboardEvent, app: IGraphicApp) => {
(app as JlDrawApp).deleteSelectedGraphics(); app.deleteGraphicAndRecord(...app.selectedGraphics);
}, },
}) })
); );
@ -389,10 +430,10 @@ export class JlDrawApp extends GraphicApp {
new KeyListener({ new KeyListener({
value: 'ArrowUp', value: 'ArrowUp',
pressTriggerAsOriginalEvent: true, pressTriggerAsOriginalEvent: true,
onPress: (e: KeyboardEvent, app: GraphicApp) => { onPress: (e: KeyboardEvent, app: IGraphicApp) => {
updateGraphicPositionOnKeyboardEvent(app, UP); updateGraphicPositionOnKeyboardEvent(app, UP);
}, },
onRelease: (e: KeyboardEvent, app: GraphicApp) => { onRelease: (e: KeyboardEvent, app: IGraphicApp) => {
recordGraphicMoveOperation(app); recordGraphicMoveOperation(app);
}, },
}) })
@ -401,10 +442,10 @@ export class JlDrawApp extends GraphicApp {
new KeyListener({ new KeyListener({
value: 'ArrowDown', value: 'ArrowDown',
pressTriggerAsOriginalEvent: true, pressTriggerAsOriginalEvent: true,
onPress: (e: KeyboardEvent, app: GraphicApp) => { onPress: (e: KeyboardEvent, app: IGraphicApp) => {
updateGraphicPositionOnKeyboardEvent(app, DOWN); updateGraphicPositionOnKeyboardEvent(app, DOWN);
}, },
onRelease: (e: KeyboardEvent, app: GraphicApp) => { onRelease: (e: KeyboardEvent, app: IGraphicApp) => {
recordGraphicMoveOperation(app); recordGraphicMoveOperation(app);
}, },
}) })
@ -413,10 +454,10 @@ export class JlDrawApp extends GraphicApp {
new KeyListener({ new KeyListener({
value: 'ArrowLeft', value: 'ArrowLeft',
pressTriggerAsOriginalEvent: true, pressTriggerAsOriginalEvent: true,
onPress: (e: KeyboardEvent, app: GraphicApp) => { onPress: (e: KeyboardEvent, app: IGraphicApp) => {
updateGraphicPositionOnKeyboardEvent(app, LEFT); updateGraphicPositionOnKeyboardEvent(app, LEFT);
}, },
onRelease: (e: KeyboardEvent, app: GraphicApp) => { onRelease: (e: KeyboardEvent, app: IGraphicApp) => {
recordGraphicMoveOperation(app); recordGraphicMoveOperation(app);
}, },
}) })
@ -425,23 +466,16 @@ export class JlDrawApp extends GraphicApp {
new KeyListener({ new KeyListener({
value: 'ArrowRight', value: 'ArrowRight',
pressTriggerAsOriginalEvent: true, pressTriggerAsOriginalEvent: true,
onPress: (e: KeyboardEvent, app: GraphicApp) => { onPress: (e: KeyboardEvent, app: IGraphicApp) => {
updateGraphicPositionOnKeyboardEvent(app, RIGHT); updateGraphicPositionOnKeyboardEvent(app, RIGHT);
}, },
onRelease: (e: KeyboardEvent, app: GraphicApp) => { onRelease: (e: KeyboardEvent, app: IGraphicApp) => {
recordGraphicMoveOperation(app); recordGraphicMoveOperation(app);
}, },
}) })
); );
} }
/**
*
*/
selectAllGraphics() {
this.updateSelected(...this.queryStore.getAllGraphics());
}
/** /**
* , * ,
* @param graphic * @param graphic
@ -452,19 +486,6 @@ export class JlDrawApp extends GraphicApp {
graphic.draggable = true; graphic.draggable = true;
} }
/**
*
*/
deleteSelectedGraphics() {
const deletes = this.deleteGraphics(...this.selectedGraphics);
if (deletes.length > 0) {
// 删除图形对象操作记录
this.opRecord.record(new GraphicDeleteOperation(this, deletes));
} else {
console.debug('没有删除元素,不记录');
}
}
updateCanvasAndRecord(data: ICanvasProperties) { updateCanvasAndRecord(data: ICanvasProperties) {
const old = this.canvas.properties.clone(); const old = this.canvas.properties.clone();
this.canvas.update(data); this.canvas.update(data);
@ -487,7 +508,7 @@ export class JlDrawApp extends GraphicApp {
let dragStartDatas: GraphicData[] = []; let dragStartDatas: GraphicData[] = [];
function handleArrowKeyMoveGraphics( function handleArrowKeyMoveGraphics(
app: GraphicApp, app: IGraphicApp,
handler: (obj: DisplayObject) => void handler: (obj: DisplayObject) => void
) { ) {
if ( if (
@ -506,7 +527,10 @@ function handleArrowKeyMoveGraphics(
} }
} }
function updateGraphicPositionOnKeyboardEvent(app: GraphicApp, dp: IPointData) { function updateGraphicPositionOnKeyboardEvent(
app: IGraphicApp,
dp: IPointData
) {
let dragStart = false; let dragStart = false;
if (dragStartDatas.length === 0) { if (dragStartDatas.length === 0) {
dragStartDatas = app.selectedGraphics.map((g) => g.saveData()); dragStartDatas = app.selectedGraphics.map((g) => g.saveData());
@ -541,7 +565,7 @@ function updateGraphicPositionOnKeyboardEvent(app: GraphicApp, dp: IPointData) {
} }
}); });
} }
function recordGraphicMoveOperation(app: GraphicApp) { function recordGraphicMoveOperation(app: IGraphicApp) {
if ( if (
dragStartDatas.length > 0 && dragStartDatas.length > 0 &&
dragStartDatas.length === app.selectedGraphics.length dragStartDatas.length === app.selectedGraphics.length
@ -570,110 +594,3 @@ function recordGraphicMoveOperation(app: GraphicApp) {
} }
dragStartDatas = []; dragStartDatas = [];
} }
/**
*
*/
export class UpdateCanvasOperation extends JlOperation {
obj: JlCanvas;
old: ICanvasProperties;
data: ICanvasProperties;
description = '';
constructor(
app: GraphicApp,
obj: JlCanvas,
old: ICanvasProperties,
data: ICanvasProperties
) {
super(app, 'update-canvas');
this.app = app;
this.obj = obj;
this.old = old;
this.data = data;
}
undo(): JlGraphic[] {
this.obj.update(this.old);
return [];
}
redo(): JlGraphic[] {
this.obj.update(this.data);
return [];
}
}
/**
*
*/
export class GraphicCreateOperation extends JlOperation {
obj: JlGraphic[];
description = '';
constructor(app: GraphicApp, obj: JlGraphic[]) {
super(app, 'graphic-create');
this.app = app;
this.obj = obj;
}
undo(): JlGraphic[] | void {
this.app.deleteGraphics(...this.obj);
}
redo(): JlGraphic[] {
this.app.addGraphics(...this.obj);
return this.obj;
}
}
/**
*
*/
export class GraphicDeleteOperation extends JlOperation {
obj: JlGraphic[];
constructor(app: GraphicApp, obj: JlGraphic[]) {
super(app, 'graphic-delete');
this.app = app;
this.obj = obj;
}
undo(): JlGraphic[] {
this.app.addGraphics(...this.obj);
return this.obj;
}
redo(): void {
this.app.deleteGraphics(...this.obj);
}
}
export class GraphicDataUpdateOperation extends JlOperation {
obj: JlGraphic[];
oldData: GraphicData[];
newData: GraphicData[];
constructor(
app: GraphicApp,
obj: JlGraphic[],
oldData: GraphicData[],
newData: GraphicData[]
) {
super(app, 'graphic-drag');
this.obj = [...obj];
this.oldData = oldData;
this.newData = newData;
}
undo(): void | JlGraphic[] {
for (let i = 0; i < this.obj.length; i++) {
const g = this.obj[i];
// g.exitChildEdit();
g.updateData(this.oldData[i]);
}
return this.obj;
}
redo(): void | JlGraphic[] {
for (let i = 0; i < this.obj.length; i++) {
const g = this.obj[i];
// g.exitChildEdit();
g.updateData(this.newData[i]);
}
return this.obj;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +1,46 @@
export * from './JlGraphicApp'; import {
export * from './JlDrawApp'; DrawAppOptions,
DrawAssistant,
GraphicDrawAssistant,
IDrawApp,
JlDrawApp,
} from './JlDrawApp';
import {
AppConsts,
GraphicApp,
GraphicAppOptions,
ICanvasProperties,
IGraphicApp,
IGraphicScene,
IGraphicStorage,
IJlCanvas,
} from './JlGraphicApp';
/**
* app
* @param options
* @returns
*/
export function newGraphicApp(options: GraphicAppOptions): IGraphicApp {
return new GraphicApp(options);
}
/**
* app
* @param options
* @returns
*/
export function newDrawApp(options: DrawAppOptions): IDrawApp {
return new JlDrawApp(options);
}
export { AppConsts, GraphicDrawAssistant };
export type {
DrawAssistant,
ICanvasProperties,
IDrawApp,
IGraphicApp,
IGraphicScene,
IGraphicStorage,
IJlCanvas,
};

View File

@ -1,5 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { GraphicApp } from '../app/JlGraphicApp';
import { JlGraphic } from './JlGraphic'; import { JlGraphic } from './JlGraphic';
/** /**
@ -101,11 +100,7 @@ export class GraphicRelation {
* *
*/ */
export class RelationManage { export class RelationManage {
app: GraphicApp;
relations: GraphicRelation[] = []; relations: GraphicRelation[] = [];
constructor(app: GraphicApp) {
this.app = app;
}
isContainsRelation( isContainsRelation(
rp1: GraphicRelationParam, rp1: GraphicRelationParam,
@ -182,4 +177,11 @@ export class RelationManage {
const relations = this.getRelationsOfGraphicAndOtherType(g, type); const relations = this.getRelationsOfGraphicAndOtherType(g, type);
relations.forEach((rl) => this.deleteRelation(rl)); relations.forEach((rl) => this.deleteRelation(rl));
} }
/**
*
*/
clear() {
this.relations.splice(0, this.relations.length);
}
} }

View File

@ -1,4 +1,3 @@
import { GraphicApp } from '../app/JlGraphicApp';
import { RelationManage } from './GraphicRelation'; import { RelationManage } from './GraphicRelation';
import { JlGraphic } from './JlGraphic'; import { JlGraphic } from './JlGraphic';
@ -67,13 +66,11 @@ export interface GraphicQueryStore {
* *
*/ */
export class GraphicStore implements GraphicQueryStore { export class GraphicStore implements GraphicQueryStore {
app: GraphicApp;
store: Map<string, JlGraphic>; store: Map<string, JlGraphic>;
relationManage: RelationManage; relationManage: RelationManage;
constructor(app: GraphicApp) { constructor() {
this.app = app;
this.store = new Map<string, JlGraphic>(); this.store = new Map<string, JlGraphic>();
this.relationManage = new RelationManage(app); this.relationManage = new RelationManage();
} }
/** /**
@ -198,4 +195,12 @@ export class GraphicStore implements GraphicQueryStore {
} }
return remove; return remove;
} }
/**
*
*/
clear() {
this.relationManage.clear();
this.store.clear();
}
} }

View File

@ -10,7 +10,7 @@ import {
Point, Point,
Rectangle, Rectangle,
} from 'pixi.js'; } from 'pixi.js';
import { AppConsts, JlCanvas } from '../app'; import { AppConsts, IJlCanvas } from '../app';
import { import {
convertRectangleToPolygonPoints, convertRectangleToPolygonPoints,
recursiveChildren, recursiveChildren,
@ -277,7 +277,7 @@ DisplayObject.prototype.getCanvas = function getCanvas() {
graphic = graphic.parent; graphic = graphic.parent;
} }
if (graphic) { if (graphic) {
return graphic as JlCanvas; return graphic as IJlCanvas;
} }
throw new Error(`图形${this.name}不在画布中`); throw new Error(`图形${this.name}不在画布中`);
}; };
@ -290,7 +290,7 @@ DisplayObject.prototype.getViewport = function getViewport() {
}; };
DisplayObject.prototype.getGraphicApp = function getGraphicApp() { DisplayObject.prototype.getGraphicApp = function getGraphicApp() {
const canvas = this.getCanvas(); const canvas = this.getCanvas();
return canvas.app; return canvas.scene.app;
}; };
DisplayObject.prototype.localToCanvasPoint = function localToCanvasPoint( DisplayObject.prototype.localToCanvasPoint = function localToCanvasPoint(
p: IPointData p: IPointData

View File

@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
declare namespace GlobalMixins { declare namespace GlobalMixins {
type JlCanvasType = import('./app').JlCanvas; type JlCanvasType = import('./app').IJlCanvas;
type CanvasProperties = import('./app').ICanvasProperties; type CanvasProperties = import('./app').ICanvasProperties;
type GraphicApp = import('./app').GraphicApp; type GraphicApp = import('./app').IGraphicApp;
type JlGraphicType = import('./core').JlGraphic; type JlGraphicType = import('./core').JlGraphic;
type GraphicData = import('./core').GraphicData; type GraphicData = import('./core').GraphicData;
type GraphicState = import('./core').GraphicState; type GraphicState = import('./core').GraphicState;

View File

@ -29,8 +29,9 @@ export class VectorText extends Text implements VectorGraphic {
* *
*/ */
setVectorFontSize(fontSize: number) { setVectorFontSize(fontSize: number) {
this.vectorFontSize = fontSize; if (this.vectorFontSize !== fontSize) {
// this.style.fontSize = fontSize; this.vectorFontSize = fontSize;
this.updateOnScaled(); this.updateOnScaled();
}
} }
} }

View File

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import EventEmitter from 'eventemitter3'; import EventEmitter from 'eventemitter3';
import { GraphicApp } from '../app'; import { IGraphicScene } from '../app';
import { CompleteMessageCliOption, IMessageClient } from './MessageBroker'; import { CompleteMessageCliOption, IMessageClient } from './MessageBroker';
export interface MessageClientEvents { export interface MessageClientEvents {
@ -14,7 +14,7 @@ export interface IMessageHandler {
/** /**
* id * id
*/ */
get App(): GraphicApp; get App(): IGraphicScene;
/** /**
* *
* @param data * @param data

View File

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import EventEmitter from 'eventemitter3'; import EventEmitter from 'eventemitter3';
import { GraphicApp } from '../app'; import { IGraphicScene } from '../app';
import { GraphicState } from '../core'; import { GraphicState } from '../core';
import { import {
IMessageHandler, IMessageHandler,
@ -94,6 +94,10 @@ export class WsMsgCli {
}); });
} }
static isInitiated(): boolean {
return !!WsMsgCli.client;
}
static emitConnectStateChangeEvent(connected: boolean) { static emitConnectStateChangeEvent(connected: boolean) {
WsMsgCli.appMsgBroker.forEach((broker) => { WsMsgCli.appMsgBroker.forEach((broker) => {
broker.app.emit('websocket-connect-state-change', connected); broker.app.emit('websocket-connect-state-change', connected);
@ -167,16 +171,16 @@ export interface AppStateSubscription {
} }
class AppMessageHandler implements IMessageHandler { class AppMessageHandler implements IMessageHandler {
app: GraphicApp; app: IGraphicScene;
sub: AppStateSubscription; sub: AppStateSubscription;
constructor(app: GraphicApp, subOptions: AppStateSubscription) { constructor(app: IGraphicScene, subOptions: AppStateSubscription) {
this.app = app; this.app = app;
if (!subOptions.messageConverter && !subOptions.messageHandle) { if (!subOptions.messageConverter && !subOptions.messageHandle) {
throw new Error(`没有消息处理器或图形状态消息转换器: ${subOptions}`); throw new Error(`没有消息处理器或图形状态消息转换器: ${subOptions}`);
} }
this.sub = subOptions; this.sub = subOptions;
} }
get App(): GraphicApp { get App(): IGraphicScene {
return this.app; return this.app;
} }
handle(data: any): void { handle(data: any): void {
@ -194,13 +198,13 @@ class AppMessageHandler implements IMessageHandler {
* APP的websocket消息代理 * APP的websocket消息代理
*/ */
export class AppWsMsgBroker { export class AppWsMsgBroker {
app: GraphicApp; app: IGraphicScene;
subscriptions: Map<string, AppMessageHandler> = new Map< subscriptions: Map<string, AppMessageHandler> = new Map<
string, string,
AppMessageHandler AppMessageHandler
>(); >();
constructor(app: GraphicApp) { constructor(app: IGraphicScene) {
this.app = app; this.app = app;
WsMsgCli.registerAppMsgBroker(this); WsMsgCli.registerAppMsgBroker(this);
} }

View File

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { GraphicApp } from '../app/JlGraphicApp'; import { IGraphicApp } from '../app/JlGraphicApp';
import { JlGraphic } from '../core/JlGraphic'; import { JlGraphic } from '../core/JlGraphic';
/** /**
@ -7,12 +7,12 @@ import { JlGraphic } from '../core/JlGraphic';
*/ */
export abstract class JlOperation { export abstract class JlOperation {
type: string; // 操作类型/名称 type: string; // 操作类型/名称
app: GraphicApp; app: IGraphicApp;
obj?: any; // 操作对象 obj?: any; // 操作对象
data?: any; // 操作数据 data?: any; // 操作数据
description?: string = ''; // 操作描述 description?: string = ''; // 操作描述
constructor(app: GraphicApp, type: string) { constructor(app: IGraphicApp, type: string) {
this.app = app; this.app = app;
this.type = type; this.type = type;
} }

View File

@ -1,31 +1,49 @@
import { GraphicApp } from '../app'; import { IGraphicScene } from '../app';
import { GraphicAnimation, JlGraphic } from '../core'; import { GraphicAnimation, JlGraphic } from '../core';
/** /**
* *
*/ */
export class AnimationManager { export class AnimationManager {
app: GraphicApp; app: IGraphicScene;
_pause: boolean;
/**
* key - graphic.id
*/
graphicAnimationMap: Map<string, Map<string, GraphicAnimation>>; graphicAnimationMap: Map<string, Map<string, GraphicAnimation>>;
constructor(app: GraphicApp) { constructor(app: IGraphicScene) {
this.app = app; this.app = app;
this._pause = false;
this.graphicAnimationMap = new Map<string, Map<string, GraphicAnimation>>(); this.graphicAnimationMap = new Map<string, Map<string, GraphicAnimation>>();
// 动画控制 // 动画控制
app.app.ticker.add((dt: number) => { app.pixi.ticker.add(this.run, this);
this.graphicAnimationMap.forEach((map) => { }
map.forEach((animation) => {
if (animation.running) { private run(dt: number) {
animation.run(dt); if (this._pause) {
} // 暂停
}); return;
}
this.graphicAnimationMap.forEach((map) => {
map.forEach((animation) => {
if (animation.running) {
animation.run(dt);
}
}); });
}); });
} }
static new(app: GraphicApp): AnimationManager { pause() {
return new AnimationManager(app); this._pause = true;
} }
resume() {
this._pause = false;
}
destroy() {
this.app.pixi.ticker.remove(this.run, this);
}
/** /**
* map * map
* @param graphic * @param graphic

View File

@ -1,5 +1,5 @@
import { DisplayObject, FederatedMouseEvent, Graphics, Point } from 'pixi.js'; import { DisplayObject, FederatedMouseEvent, Graphics, Point } from 'pixi.js';
import { GraphicApp, JlCanvas } from '../app'; import { IGraphicScene } from '../app';
import { JlGraphic } from '../core'; import { JlGraphic } from '../core';
import { import {
AppDragEvent, AppDragEvent,
@ -73,7 +73,7 @@ export class CommonMouseTool extends AppInteractionPlugin {
rightTarget: DisplayObject | null = null; rightTarget: DisplayObject | null = null;
constructor(graphicApp: GraphicApp) { constructor(graphicApp: IGraphicScene) {
super(CommonMouseTool.Name, graphicApp); super(CommonMouseTool.Name, graphicApp);
this.options = new CompleteMouseToolOptions(); this.options = new CompleteMouseToolOptions();
@ -93,7 +93,7 @@ export class CommonMouseTool extends AppInteractionPlugin {
}); });
} }
static new(app: GraphicApp) { static new(app: IGraphicScene) {
return new CommonMouseTool(app); return new CommonMouseTool(app);
} }
@ -173,19 +173,20 @@ export class CommonMouseTool extends AppInteractionPlugin {
} }
setCursor(e: FederatedMouseEvent) { setCursor(e: FederatedMouseEvent) {
this.rightTarget = e.target as DisplayObject; const target = e.target as DisplayObject;
if (e.target instanceof JlCanvas && this.app.app.view.style) { this.rightTarget = target;
this.app.app.view.style.cursor = 'grab'; if (target.isCanvas() && this.app.pixi.view.style) {
this.app.pixi.view.style.cursor = 'grab';
} }
} }
resumeCursor() { resumeCursor() {
if ( if (
this.rightTarget && this.rightTarget &&
this.rightTarget instanceof JlCanvas && this.rightTarget.isCanvas() &&
this.app.app.view.style this.app.pixi.view.style
) { ) {
this.app.app.view.style.cursor = 'inherit'; this.app.pixi.view.style.cursor = 'inherit';
} }
this.rightTarget = null; this.rightTarget = null;
} }
@ -248,7 +249,7 @@ export class CommonMouseTool extends AppInteractionPlugin {
} }
} else { } else {
// 非多选 // 非多选
if (e.target instanceof JlCanvas) { if ((e.target as DisplayObject).isCanvas()) {
this.app.updateSelected(); this.app.updateSelected();
} else { } else {
if ( if (

View File

@ -1,18 +1,18 @@
import { Container, FederatedPointerEvent, Point } from 'pixi.js'; import { Container, FederatedPointerEvent, Point } from 'pixi.js';
import { GraphicApp, GraphicCreateOperation } from '../app'; import { IGraphicScene } from '../app';
import { JlGraphic } from '../core'; import { JlGraphic } from '../core';
import { KeyListener } from './KeyboardPlugin'; import { KeyListener } from './KeyboardPlugin';
export class GraphicCopyPlugin { export class GraphicCopyPlugin {
container: Container; container: Container;
app: GraphicApp; scene: IGraphicScene;
keyListeners: KeyListener[]; keyListeners: KeyListener[];
copys: JlGraphic[]; copys: JlGraphic[];
start?: Point; start?: Point;
running = false; running = false;
moveLimit?: 'x' | 'y'; moveLimit?: 'x' | 'y';
constructor(app: GraphicApp) { constructor(scene: IGraphicScene) {
this.app = app; this.scene = scene;
this.container = new Container(); this.container = new Container();
this.copys = []; this.copys = [];
this.keyListeners = []; this.keyListeners = [];
@ -57,15 +57,15 @@ export class GraphicCopyPlugin {
init(): void { init(): void {
if (this.running) return; if (this.running) return;
if (this.app.selectedGraphics.length === 0) { if (this.scene.selectedGraphics.length === 0) {
throw new Error('没有选中图形,复制取消'); throw new Error('没有选中图形,复制取消');
} }
this.running = true; this.running = true;
this.copys = []; this.copys = [];
this.container.alpha = 0.5; this.container.alpha = 0.5;
this.app.canvas.addChild(this.container); this.scene.canvas.addChild(this.container);
const app = this.app; const app = this.scene;
this.app.selectedGraphics.forEach((g) => { this.scene.selectedGraphics.forEach((g) => {
const template = app.getGraphicTemplatesByType(g.type); const template = app.getGraphicTemplatesByType(g.type);
const clone = template.clone(g); const clone = template.clone(g);
this.copys.push(clone); this.copys.push(clone);
@ -73,11 +73,11 @@ export class GraphicCopyPlugin {
this.container.addChild(clone); this.container.addChild(clone);
clone.repaint(); clone.repaint();
}); });
this.app.canvas.on('mousemove', this.onPointerMove, this); this.scene.canvas.on('mousemove', this.onPointerMove, this);
this.app.canvas.on('mouseup', this.onFinish, this); this.scene.canvas.on('mouseup', this.onFinish, this);
this.app.canvas.on('rightup', this.cancle, this); this.scene.canvas.on('rightup', this.cancle, this);
this.keyListeners.forEach((kl) => { this.keyListeners.forEach((kl) => {
this.app.addKeyboardListener(kl); this.scene.app.addKeyboardListener(kl);
}); });
} }
@ -87,17 +87,17 @@ export class GraphicCopyPlugin {
this.moveLimit = undefined; this.moveLimit = undefined;
this.copys = []; this.copys = [];
this.container.removeChildren(); this.container.removeChildren();
this.app.canvas.removeChild(this.container); this.scene.canvas.removeChild(this.container);
this.app.canvas.off('mousemove', this.onPointerMove, this); this.scene.canvas.off('mousemove', this.onPointerMove, this);
this.app.canvas.off('mouseup', this.onFinish, this); this.scene.canvas.off('mouseup', this.onFinish, this);
this.app.canvas.off('rightup', this.cancle, this); this.scene.canvas.off('rightup', this.cancle, this);
this.keyListeners.forEach((kl) => { this.keyListeners.forEach((kl) => {
this.app.removeKeyboardListener(kl); this.scene.app.removeKeyboardListener(kl);
}); });
} }
onPointerMove(e: FederatedPointerEvent): void { onPointerMove(e: FederatedPointerEvent): void {
const cp = this.app.toCanvasCoordinates(e.global); const cp = this.scene.toCanvasCoordinates(e.global);
if (!this.start) { if (!this.start) {
this.start = cp; this.start = cp;
} else { } else {
@ -125,17 +125,15 @@ export class GraphicCopyPlugin {
g.position.x += this.container.position.x; g.position.x += this.container.position.x;
g.position.y += this.container.position.y; g.position.y += this.container.position.y;
}); });
this.app.addGraphics(...this.copys); this.scene.app.addGraphicAndRecord(...this.copys);
// 创建图形对象操作记录 this.scene.detectRelations();
this.app.opRecord.record(new GraphicCreateOperation(this.app, this.copys)); this.scene.updateSelected(...this.copys);
this.app.detectRelations();
this.app.updateSelected(...this.copys);
this.clear(); this.clear();
} }
cancle(): void { cancle(): void {
console.log('复制操作取消'); console.log('复制操作取消');
this.app.canvas.removeChild(this.container); this.scene.canvas.removeChild(this.container);
this.clear(); this.clear();
} }
} }

View File

@ -12,7 +12,7 @@ import {
InteractionPluginType, InteractionPluginType,
KeyListener, KeyListener,
} from '.'; } from '.';
import { GraphicApp } from '../app'; import { IGraphicScene } from '../app';
import { JlGraphic } from '../core'; import { JlGraphic } from '../core';
import { AbsorbablePosition, VectorText } from '../graphic'; import { AbsorbablePosition, VectorText } from '../graphic';
import { DraggablePoint } from '../graphic/DraggablePoint'; import { DraggablePoint } from '../graphic/DraggablePoint';
@ -164,7 +164,7 @@ export class GraphicTransformPlugin extends InteractionPluginBase {
apContainer: Container; apContainer: Container;
static AbsorbablePosisiontsName = '__AbsorbablePosisionts'; static AbsorbablePosisiontsName = '__AbsorbablePosisionts';
constructor(app: GraphicApp) { constructor(app: IGraphicScene) {
super(app, GraphicTransformPlugin.Name, InteractionPluginType.Other); super(app, GraphicTransformPlugin.Name, InteractionPluginType.Other);
this.apContainer = new Container(); this.apContainer = new Container();
this.apContainer.name = GraphicTransformPlugin.AbsorbablePosisiontsName; this.apContainer.name = GraphicTransformPlugin.AbsorbablePosisiontsName;
@ -205,7 +205,7 @@ export class GraphicTransformPlugin extends InteractionPluginBase {
return aps; return aps;
} }
static new(app: GraphicApp) { static new(app: IGraphicScene) {
return new GraphicTransformPlugin(app); return new GraphicTransformPlugin(app);
} }

View File

@ -4,7 +4,11 @@ import {
FederatedPointerEvent, FederatedPointerEvent,
Point, Point,
} from 'pixi.js'; } from 'pixi.js';
import { GraphicApp, IGraphicAppConfig } from '../app/JlGraphicApp'; import {
IGraphicApp,
IGraphicAppConfig,
IGraphicScene,
} from '../app/JlGraphicApp';
import { JlGraphic } from '../core/JlGraphic'; import { JlGraphic } from '../core/JlGraphic';
export enum InteractionPluginType { export enum InteractionPluginType {
@ -19,7 +23,7 @@ export enum InteractionPluginType {
export interface InteractionPlugin { export interface InteractionPlugin {
readonly _type: string; readonly _type: string;
name: string; // 唯一标识 name: string; // 唯一标识
app: GraphicApp; app: IGraphicScene;
/** /**
* *
@ -41,10 +45,10 @@ export interface InteractionPlugin {
export abstract class InteractionPluginBase implements InteractionPlugin { export abstract class InteractionPluginBase implements InteractionPlugin {
readonly _type: string; readonly _type: string;
name: string; // 唯一标识 name: string; // 唯一标识
app: GraphicApp; app: IGraphicScene;
_pause: boolean; _pause: boolean;
constructor(app: GraphicApp, name: string, type: string) { constructor(app: IGraphicScene, name: string, type: string) {
this._type = type; this._type = type;
this.app = app; this.app = app;
this.name = name; this.name = name;
@ -89,19 +93,19 @@ export abstract class InteractionPluginBase implements InteractionPlugin {
} }
export abstract class OtherInteractionPlugin extends InteractionPluginBase { export abstract class OtherInteractionPlugin extends InteractionPluginBase {
constructor(app: GraphicApp, name: string) { constructor(app: IGraphicScene, name: string) {
super(app, name, InteractionPluginType.Other); super(app, name, InteractionPluginType.Other);
} }
} }
export class AppDragEvent { export class AppDragEvent {
app: GraphicApp; app: IGraphicScene;
type: 'start' | 'move' | 'end'; type: 'start' | 'move' | 'end';
target: DisplayObject; target: DisplayObject;
original: FederatedPointerEvent; original: FederatedPointerEvent;
start: Point; // 画布坐标 start: Point; // 画布坐标
constructor( constructor(
app: GraphicApp, app: IGraphicScene,
type: 'start' | 'move' | 'end', type: 'start' | 'move' | 'end',
target: DisplayObject, target: DisplayObject,
original: FederatedPointerEvent, original: FederatedPointerEvent,
@ -191,7 +195,7 @@ export class DragPlugin extends OtherInteractionPlugin {
start: Point | null = null; start: Point | null = null;
startClientPoint: Point | null = null; startClientPoint: Point | null = null;
drag = false; drag = false;
constructor(app: GraphicApp) { constructor(app: IGraphicScene) {
super(app, DragPlugin.Name); super(app, DragPlugin.Name);
app.on('options-update', (options: IGraphicAppConfig) => { app.on('options-update', (options: IGraphicAppConfig) => {
if (options.threshold !== undefined) { if (options.threshold !== undefined) {
@ -199,7 +203,7 @@ export class DragPlugin extends OtherInteractionPlugin {
} }
}); });
} }
static new(app: GraphicApp) { static new(app: IGraphicScene) {
return new DragPlugin(app); return new DragPlugin(app);
} }
bind(): void { bind(): void {
@ -301,11 +305,11 @@ export class ViewportMovePlugin extends OtherInteractionPlugin {
moveSpeedx = 0; moveSpeedx = 0;
moveSpeedy = 0; moveSpeedy = 0;
constructor(app: GraphicApp) { constructor(app: IGraphicScene) {
super(app, ViewportMovePlugin.Name); super(app, ViewportMovePlugin.Name);
} }
static new(app: GraphicApp): ViewportMovePlugin { static new(app: IGraphicScene): ViewportMovePlugin {
return new ViewportMovePlugin(app); return new ViewportMovePlugin(app);
} }
pause(): void { pause(): void {
@ -393,7 +397,7 @@ export class ViewportMovePlugin extends OtherInteractionPlugin {
* *
*/ */
export abstract class AppInteractionPlugin extends InteractionPluginBase { export abstract class AppInteractionPlugin extends InteractionPluginBase {
constructor(name: string, app: GraphicApp) { constructor(name: string, app: IGraphicScene) {
super(app, name, InteractionPluginType.App); super(app, name, InteractionPluginType.App);
} }
@ -413,10 +417,10 @@ export abstract class GraphicInteractionPlugin<G extends JlGraphic>
implements InteractionPlugin implements InteractionPlugin
{ {
readonly _type = InteractionPluginType.Graphic; readonly _type = InteractionPluginType.Graphic;
app: GraphicApp; app: IGraphicApp;
name: string; // 唯一标识 name: string; // 唯一标识
_pause: boolean; _pause: boolean;
constructor(name: string, app: GraphicApp) { constructor(name: string, app: IGraphicApp) {
this.app = app; this.app = app;
this.name = name; this.name = name;
this._pause = true; this._pause = true;

View File

@ -1,4 +1,4 @@
import { GraphicApp } from '../app/JlGraphicApp'; import { IGraphicApp } from '../app/JlGraphicApp';
let target: Node | undefined; let target: Node | undefined;
@ -62,7 +62,7 @@ export class GlobalKeyboardHelper {
const GlobalKeyboardPlugin = new GlobalKeyboardHelper(); const GlobalKeyboardPlugin = new GlobalKeyboardHelper();
export class JlGraphicAppKeyboardPlugin { export class JlGraphicAppKeyboardPlugin {
app: GraphicApp; app: IGraphicApp;
/** /**
* Map<key.code|key.key|key.keyCode, Map<KeyListener.identifier, KeyListener>> * Map<key.code|key.key|key.keyCode, Map<KeyListener.identifier, KeyListener>>
*/ */
@ -75,7 +75,7 @@ export class JlGraphicAppKeyboardPlugin {
KeyListener[] KeyListener[]
>(); // 键值监听栈(多次注册相同的监听会把之前注册的监听器入栈,移除最新的监听会从栈中弹出一个作为指定事件监听处理器) >(); // 键值监听栈(多次注册相同的监听会把之前注册的监听器入栈,移除最新的监听会从栈中弹出一个作为指定事件监听处理器)
constructor(app: GraphicApp) { constructor(app: IGraphicApp) {
this.app = app; this.app = app;
GlobalKeyboardPlugin.registerGAKPlugin(this); GlobalKeyboardPlugin.registerGAKPlugin(this);
const onMouseUpdateTarget = (e: MouseEvent) => { const onMouseUpdateTarget = (e: MouseEvent) => {
@ -85,7 +85,7 @@ export class JlGraphicAppKeyboardPlugin {
}; };
const keydownHandle = (e: KeyboardEvent) => { const keydownHandle = (e: KeyboardEvent) => {
// console.debug(e.key, e.code, e.keyCode); // console.debug(e.key, e.code, e.keyCode);
if (target && target == this.app.dom.getElementsByTagName('canvas')[0]) { if (target && target == this.app.dom?.getElementsByTagName('canvas')[0]) {
const listenerMap = this.getKeyListener(e); const listenerMap = this.getKeyListener(e);
listenerMap?.forEach((listener) => { listenerMap?.forEach((listener) => {
if (!listener.global) { if (!listener.global) {
@ -95,7 +95,7 @@ export class JlGraphicAppKeyboardPlugin {
} }
}; };
const keyupHandle = (e: KeyboardEvent) => { const keyupHandle = (e: KeyboardEvent) => {
if (target && target == this.app.dom.getElementsByTagName('canvas')[0]) { if (target && target == this.app.dom?.getElementsByTagName('canvas')[0]) {
const listenerMap = this.getKeyListener(e); const listenerMap = this.getKeyListener(e);
listenerMap?.forEach((listener) => { listenerMap?.forEach((listener) => {
if (!listener.global) { if (!listener.global) {
@ -202,7 +202,7 @@ export class JlGraphicAppKeyboardPlugin {
} }
} }
type KeyboardKeyHandler = (e: KeyboardEvent, app: GraphicApp) => void; type KeyboardKeyHandler = (e: KeyboardEvent, app: IGraphicApp) => void;
export enum CombinationKey { export enum CombinationKey {
Ctrl = 'Ctrl', Ctrl = 'Ctrl',
@ -300,7 +300,7 @@ export class KeyListener {
this.options.pressTriggerAsOriginalEvent = v; this.options.pressTriggerAsOriginalEvent = v;
} }
press(e: KeyboardEvent, app: GraphicApp): void { press(e: KeyboardEvent, app: IGraphicApp): void {
if (!this.checkCombinations(e)) { if (!this.checkCombinations(e)) {
console.debug('组合键不匹配, 不执行press', e, this); console.debug('组合键不匹配, 不执行press', e, this);
return; return;
@ -335,7 +335,7 @@ export class KeyListener {
return false; return false;
} }
release(e: KeyboardEvent, app: GraphicApp): void { release(e: KeyboardEvent, app: IGraphicApp): void {
if (this.isPress) { if (this.isPress) {
// console.log('Keyup : ', e.key, e); // console.log('Keyup : ', e.key, e);
this.isPress = false; this.isPress = false;

View File

@ -1,5 +1,5 @@
import { Color, Container, Graphics, Point, Rectangle, Text } from 'pixi.js'; import { Color, Container, Graphics, Point, Rectangle, Text } from 'pixi.js';
import { GraphicApp } from '../app'; import { IGraphicScene } from '../app';
import { OutOfBound } from '../utils'; import { OutOfBound } from '../utils';
import { import {
DefaultWhiteMenuOptions, DefaultWhiteMenuOptions,
@ -12,10 +12,10 @@ import {
} from './Menu'; } from './Menu';
export class ContextMenuPlugin { export class ContextMenuPlugin {
app: GraphicApp; app: IGraphicScene;
contextMenuMap: Map<string, ContextMenu> = new Map<string, ContextMenu>(); contextMenuMap: Map<string, ContextMenu> = new Map<string, ContextMenu>();
constructor(app: GraphicApp) { constructor(app: IGraphicScene) {
this.app = app; this.app = app;
const canvas = this.app.canvas; const canvas = this.app.canvas;
canvas.on('pointerdown', () => { canvas.on('pointerdown', () => {
@ -52,7 +52,7 @@ export class ContextMenuPlugin {
open(menu: ContextMenu, global: Point) { open(menu: ContextMenu, global: Point) {
if (!menu.opened) { if (!menu.opened) {
menu.opened = true; menu.opened = true;
this.app.app.stage.addChild(menu); this.app.pixi.stage.addChild(menu);
} }
// 处理超出显示范围 // 处理超出显示范围
const screenHeight = this.screenHeight; const screenHeight = this.screenHeight;
@ -88,7 +88,7 @@ export class ContextMenuPlugin {
close(menu: ContextMenu) { close(menu: ContextMenu) {
if (menu.opened) { if (menu.opened) {
menu.opened = false; menu.opened = false;
this.app.app.stage.removeChild(menu); this.app.pixi.stage.removeChild(menu);
} }
} }
/** /**
@ -453,6 +453,12 @@ class MenuGroup extends Container {
this.config.items.forEach((item) => { this.config.items.forEach((item) => {
this.items.push(new ContextMenuItem(this.menu, item)); this.items.push(new ContextMenuItem(this.menu, item));
}); });
if (this.items.length === 0) {
console.error('菜单group为空', this.config, this.menu);
throw new Error(
`{name=${this.menu.name}}的菜单的group为{name=${this.config.name}}的条目为空!`
);
}
this.addChild(...this.items); this.addChild(...this.items);
} }