@sequent-org/moodboard 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +44 -0
- package/src/assets/icons/README.md +105 -0
- package/src/assets/icons/attachments.svg +3 -0
- package/src/assets/icons/clear.svg +5 -0
- package/src/assets/icons/comments.svg +3 -0
- package/src/assets/icons/emoji.svg +6 -0
- package/src/assets/icons/frame.svg +3 -0
- package/src/assets/icons/image.svg +3 -0
- package/src/assets/icons/note.svg +3 -0
- package/src/assets/icons/pan.svg +3 -0
- package/src/assets/icons/pencil.svg +3 -0
- package/src/assets/icons/redo.svg +3 -0
- package/src/assets/icons/select.svg +9 -0
- package/src/assets/icons/shapes.svg +3 -0
- package/src/assets/icons/text-add.svg +3 -0
- package/src/assets/icons/topbar/README.md +39 -0
- package/src/assets/icons/topbar/grid-cross.svg +6 -0
- package/src/assets/icons/topbar/grid-dot.svg +3 -0
- package/src/assets/icons/topbar/grid-line.svg +3 -0
- package/src/assets/icons/topbar/grid-off.svg +3 -0
- package/src/assets/icons/topbar/paint.svg +3 -0
- package/src/assets/icons/undo.svg +3 -0
- package/src/core/ApiClient.js +309 -0
- package/src/core/EventBus.js +42 -0
- package/src/core/HistoryManager.js +261 -0
- package/src/core/KeyboardManager.js +710 -0
- package/src/core/PixiEngine.js +439 -0
- package/src/core/SaveManager.js +381 -0
- package/src/core/StateManager.js +64 -0
- package/src/core/commands/BaseCommand.js +68 -0
- package/src/core/commands/CopyObjectCommand.js +44 -0
- package/src/core/commands/CreateObjectCommand.js +46 -0
- package/src/core/commands/DeleteObjectCommand.js +146 -0
- package/src/core/commands/EditFileNameCommand.js +107 -0
- package/src/core/commands/GroupMoveCommand.js +47 -0
- package/src/core/commands/GroupReorderZCommand.js +74 -0
- package/src/core/commands/GroupResizeCommand.js +37 -0
- package/src/core/commands/GroupRotateCommand.js +41 -0
- package/src/core/commands/MoveObjectCommand.js +89 -0
- package/src/core/commands/PasteObjectCommand.js +103 -0
- package/src/core/commands/ReorderZCommand.js +45 -0
- package/src/core/commands/ResizeObjectCommand.js +135 -0
- package/src/core/commands/RotateObjectCommand.js +70 -0
- package/src/core/commands/index.js +14 -0
- package/src/core/events/Events.js +147 -0
- package/src/core/index.js +1632 -0
- package/src/core/rendering/GeometryUtils.js +89 -0
- package/src/core/rendering/HitTestManager.js +186 -0
- package/src/core/rendering/LayerManager.js +137 -0
- package/src/core/rendering/ObjectRenderer.js +363 -0
- package/src/core/rendering/PixiRenderer.js +140 -0
- package/src/core/rendering/index.js +9 -0
- package/src/grid/BaseGrid.js +164 -0
- package/src/grid/CrossGrid.js +75 -0
- package/src/grid/DotGrid.js +148 -0
- package/src/grid/GridFactory.js +173 -0
- package/src/grid/LineGrid.js +115 -0
- package/src/index.js +2 -0
- package/src/moodboard/ActionHandler.js +114 -0
- package/src/moodboard/DataManager.js +114 -0
- package/src/moodboard/MoodBoard.js +359 -0
- package/src/moodboard/WorkspaceManager.js +103 -0
- package/src/objects/BaseObject.js +1 -0
- package/src/objects/CommentObject.js +115 -0
- package/src/objects/DrawingObject.js +114 -0
- package/src/objects/EmojiObject.js +98 -0
- package/src/objects/FileObject.js +318 -0
- package/src/objects/FrameObject.js +127 -0
- package/src/objects/ImageObject.js +72 -0
- package/src/objects/NoteObject.js +227 -0
- package/src/objects/ObjectFactory.js +61 -0
- package/src/objects/ShapeObject.js +134 -0
- package/src/objects/StampObject.js +0 -0
- package/src/objects/StickerObject.js +0 -0
- package/src/objects/TextObject.js +123 -0
- package/src/services/BoardService.js +85 -0
- package/src/services/FileUploadService.js +398 -0
- package/src/services/FrameService.js +138 -0
- package/src/services/ImageUploadService.js +246 -0
- package/src/services/ZOrderManager.js +50 -0
- package/src/services/ZoomPanController.js +78 -0
- package/src/src.7z +0 -0
- package/src/src.zip +0 -0
- package/src/src2.zip +0 -0
- package/src/tools/AlignmentGuides.js +326 -0
- package/src/tools/BaseTool.js +257 -0
- package/src/tools/ResizeHandles.js +381 -0
- package/src/tools/ToolManager.js +580 -0
- package/src/tools/board-tools/PanTool.js +43 -0
- package/src/tools/board-tools/ZoomTool.js +393 -0
- package/src/tools/object-tools/DrawingTool.js +404 -0
- package/src/tools/object-tools/PlacementTool.js +1005 -0
- package/src/tools/object-tools/SelectTool.js +2183 -0
- package/src/tools/object-tools/TextTool.js +416 -0
- package/src/tools/object-tools/selection/BoxSelectController.js +105 -0
- package/src/tools/object-tools/selection/GeometryUtils.js +101 -0
- package/src/tools/object-tools/selection/GroupDragController.js +61 -0
- package/src/tools/object-tools/selection/GroupResizeController.js +90 -0
- package/src/tools/object-tools/selection/GroupRotateController.js +61 -0
- package/src/tools/object-tools/selection/HandlesSync.js +96 -0
- package/src/tools/object-tools/selection/ResizeController.js +68 -0
- package/src/tools/object-tools/selection/RotateController.js +58 -0
- package/src/tools/object-tools/selection/SelectionModel.js +42 -0
- package/src/tools/object-tools/selection/SimpleDragController.js +45 -0
- package/src/ui/CommentPopover.js +187 -0
- package/src/ui/ContextMenu.js +340 -0
- package/src/ui/FilePropertiesPanel.js +298 -0
- package/src/ui/FramePropertiesPanel.js +462 -0
- package/src/ui/HtmlHandlesLayer.js +778 -0
- package/src/ui/HtmlTextLayer.js +279 -0
- package/src/ui/MapPanel.js +290 -0
- package/src/ui/NotePropertiesPanel.js +502 -0
- package/src/ui/SaveStatus.js +250 -0
- package/src/ui/TextPropertiesPanel.js +911 -0
- package/src/ui/Toolbar.js +1118 -0
- package/src/ui/Topbar.js +220 -0
- package/src/ui/ZoomPanel.js +116 -0
- package/src/ui/styles/workspace.css +854 -0
- package/src/utils/colors.js +0 -0
- package/src/utils/geometry.js +0 -0
- package/src/utils/iconLoader.js +270 -0
- package/src/utils/objectIdGenerator.js +17 -0
- package/src/utils/topbarIconLoader.js +114 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import * as PIXI from 'pixi.js';
|
|
2
|
+
import { ObjectFactory } from '../../objects/ObjectFactory.js';
|
|
3
|
+
import { GeometryUtils } from './GeometryUtils.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Рендерер объектов
|
|
7
|
+
* Отвечает за создание, обновление и удаление PIXI объектов
|
|
8
|
+
*/
|
|
9
|
+
export class ObjectRenderer {
|
|
10
|
+
constructor(objectsMap) {
|
|
11
|
+
this.objects = objectsMap; // Map<id, pixiObject> из PixiEngine
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Создать объект на холсте
|
|
16
|
+
* @param {Object} objectData - Данные объекта
|
|
17
|
+
* @returns {PIXI.DisplayObject|null} Созданный PIXI объект или null
|
|
18
|
+
*/
|
|
19
|
+
createObject(objectData) {
|
|
20
|
+
let pixiObject;
|
|
21
|
+
|
|
22
|
+
// Создаем объект через фабрику
|
|
23
|
+
const instance = ObjectFactory.create(objectData.type, objectData);
|
|
24
|
+
if (instance) {
|
|
25
|
+
pixiObject = instance.getPixi();
|
|
26
|
+
this._setupObjectMetadata(pixiObject, objectData, instance);
|
|
27
|
+
} else {
|
|
28
|
+
console.warn(`Unknown object type: ${objectData.type}`);
|
|
29
|
+
pixiObject = this._createDefaultObject(objectData);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (pixiObject) {
|
|
33
|
+
this._setupObjectProperties(pixiObject, objectData);
|
|
34
|
+
this._setupObjectTransform(pixiObject, objectData);
|
|
35
|
+
// Объект добавляется в слой через PixiEngine, здесь только сохраняем в Map
|
|
36
|
+
this.objects.set(objectData.id, pixiObject);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return pixiObject;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Настройка метаданных объекта
|
|
44
|
+
* @param {PIXI.DisplayObject} pixiObject - PIXI объект
|
|
45
|
+
* @param {Object} objectData - Данные объекта
|
|
46
|
+
* @param {Object} instance - Экземпляр объекта
|
|
47
|
+
* @private
|
|
48
|
+
*/
|
|
49
|
+
_setupObjectMetadata(pixiObject, objectData, instance) {
|
|
50
|
+
const prevMb = pixiObject._mb || {};
|
|
51
|
+
pixiObject._mb = {
|
|
52
|
+
...prevMb,
|
|
53
|
+
objectId: objectData.id,
|
|
54
|
+
type: objectData.type,
|
|
55
|
+
properties: objectData.properties || {},
|
|
56
|
+
instance
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Настройка базовых свойств объекта
|
|
62
|
+
* @param {PIXI.DisplayObject} pixiObject - PIXI объект
|
|
63
|
+
* @param {Object} objectData - Данные объекта
|
|
64
|
+
* @private
|
|
65
|
+
*/
|
|
66
|
+
_setupObjectProperties(pixiObject, objectData) {
|
|
67
|
+
pixiObject.x = objectData.position.x;
|
|
68
|
+
pixiObject.y = objectData.position.y;
|
|
69
|
+
pixiObject.eventMode = 'static';
|
|
70
|
+
pixiObject.cursor = 'pointer';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Настройка трансформации объекта
|
|
75
|
+
* @param {PIXI.DisplayObject} pixiObject - PIXI объект
|
|
76
|
+
* @param {Object} objectData - Данные объекта
|
|
77
|
+
* @private
|
|
78
|
+
*/
|
|
79
|
+
_setupObjectTransform(pixiObject, objectData) {
|
|
80
|
+
// Устанавливаем центр вращения
|
|
81
|
+
if (pixiObject.anchor !== undefined) {
|
|
82
|
+
pixiObject.anchor.set(0.5, 0.5);
|
|
83
|
+
} else if (pixiObject instanceof PIXI.Graphics) {
|
|
84
|
+
const bounds = pixiObject.getBounds();
|
|
85
|
+
const pivotX = bounds.width / 2;
|
|
86
|
+
const pivotY = bounds.height / 2;
|
|
87
|
+
pixiObject.pivot.set(pivotX, pivotY);
|
|
88
|
+
|
|
89
|
+
// Компенсируем смещение pivot
|
|
90
|
+
const needsCompensation = !objectData.transform || !objectData.transform.pivotCompensated;
|
|
91
|
+
if (needsCompensation) {
|
|
92
|
+
pixiObject.x += pivotX;
|
|
93
|
+
pixiObject.y += pivotY;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Применяем поворот
|
|
98
|
+
if (objectData.transform && objectData.transform.rotation !== undefined) {
|
|
99
|
+
const angleRadians = GeometryUtils.degreesToRadians(objectData.transform.rotation);
|
|
100
|
+
pixiObject.rotation = angleRadians;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Создать объект по умолчанию для неизвестных типов
|
|
108
|
+
* @param {Object} objectData - Данные объекта
|
|
109
|
+
* @returns {PIXI.Graphics} PIXI Graphics объект
|
|
110
|
+
* @private
|
|
111
|
+
*/
|
|
112
|
+
_createDefaultObject(objectData) {
|
|
113
|
+
const graphics = new PIXI.Graphics();
|
|
114
|
+
graphics.beginFill(0xFF0000, 0.5);
|
|
115
|
+
graphics.drawRect(0, 0, objectData.width || 100, objectData.height || 100);
|
|
116
|
+
graphics.endFill();
|
|
117
|
+
return graphics;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Удалить объект с холста
|
|
122
|
+
* @param {string} objectId - ID объекта
|
|
123
|
+
*/
|
|
124
|
+
removeObject(objectId) {
|
|
125
|
+
const pixiObject = this.objects.get(objectId);
|
|
126
|
+
if (pixiObject) {
|
|
127
|
+
// Объект удаляется из слоя через PixiEngine, здесь только удаляем из Map
|
|
128
|
+
this.objects.delete(objectId);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Обновить размер объекта
|
|
134
|
+
* @param {string} objectId - ID объекта
|
|
135
|
+
* @param {Object} size - Новый размер {width, height}
|
|
136
|
+
* @param {string} objectType - Тип объекта
|
|
137
|
+
*/
|
|
138
|
+
updateObjectSize(objectId, size, objectType = null) {
|
|
139
|
+
const pixiObject = this.objects.get(objectId);
|
|
140
|
+
if (!pixiObject) return;
|
|
141
|
+
|
|
142
|
+
console.log(`🎨 Обновляем размер объекта ${objectId}, тип: ${objectType}`);
|
|
143
|
+
|
|
144
|
+
// Делегируем изменение размера объекту
|
|
145
|
+
const meta = pixiObject._mb || {};
|
|
146
|
+
if (meta.instance && typeof meta.instance.updateSize === 'function') {
|
|
147
|
+
meta.instance.updateSize(size);
|
|
148
|
+
} else if (pixiObject instanceof PIXI.Graphics) {
|
|
149
|
+
this._recreateGraphicsObject(pixiObject, size, objectType);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Пересоздать Graphics объект с новым размером
|
|
155
|
+
* @param {PIXI.Graphics} pixiObject - PIXI Graphics объект
|
|
156
|
+
* @param {Object} size - Новый размер
|
|
157
|
+
* @param {string} objectType - Тип объекта
|
|
158
|
+
* @private
|
|
159
|
+
*/
|
|
160
|
+
_recreateGraphicsObject(pixiObject, size, objectType = null) {
|
|
161
|
+
pixiObject.clear();
|
|
162
|
+
|
|
163
|
+
console.log(`🔄 Пересоздаем Graphics объект, тип: ${objectType}`);
|
|
164
|
+
|
|
165
|
+
if (objectType === 'drawing') {
|
|
166
|
+
this._redrawDrawingObject(pixiObject, size);
|
|
167
|
+
} else {
|
|
168
|
+
this._redrawDefaultObject(pixiObject, size);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Устанавливаем pivot в центр
|
|
172
|
+
const pivotX = size.width / 2;
|
|
173
|
+
const pivotY = size.height / 2;
|
|
174
|
+
pixiObject.pivot.set(pivotX, pivotY);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Перерисовать drawing объект
|
|
179
|
+
* @param {PIXI.Graphics} pixiObject - PIXI Graphics объект
|
|
180
|
+
* @param {Object} size - Размер
|
|
181
|
+
* @private
|
|
182
|
+
*/
|
|
183
|
+
_redrawDrawingObject(pixiObject, size) {
|
|
184
|
+
const meta = pixiObject._mb || {};
|
|
185
|
+
const props = meta.properties || {};
|
|
186
|
+
const color = props.strokeColor ?? 0x111827;
|
|
187
|
+
const widthPx = props.strokeWidth ?? 2;
|
|
188
|
+
const alpha = props.mode === 'marker' ? 0.6 : 1;
|
|
189
|
+
const points = Array.isArray(props.points) ? props.points : [];
|
|
190
|
+
const baseW = props.baseWidth || size.width || 1;
|
|
191
|
+
const baseH = props.baseHeight || size.height || 1;
|
|
192
|
+
const scaleX = baseW ? (size.width / baseW) : 1;
|
|
193
|
+
const scaleY = baseH ? (size.height / baseH) : 1;
|
|
194
|
+
const lineWidth = props.mode === 'marker' ? widthPx * 2 : widthPx;
|
|
195
|
+
|
|
196
|
+
pixiObject.lineStyle({
|
|
197
|
+
width: lineWidth,
|
|
198
|
+
color,
|
|
199
|
+
alpha,
|
|
200
|
+
cap: 'round',
|
|
201
|
+
join: 'round',
|
|
202
|
+
miterLimit: 2,
|
|
203
|
+
alignment: 0.5
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
pixiObject.blendMode = props.mode === 'marker' ? PIXI.BLEND_MODES.LIGHTEN : PIXI.BLEND_MODES.NORMAL;
|
|
207
|
+
|
|
208
|
+
if (points.length > 0) {
|
|
209
|
+
this._drawPoints(pixiObject, points, scaleX, scaleY);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Нарисовать точки для drawing объекта
|
|
215
|
+
* @param {PIXI.Graphics} pixiObject - PIXI Graphics объект
|
|
216
|
+
* @param {Array} points - Массив точек
|
|
217
|
+
* @param {number} scaleX - Масштаб по X
|
|
218
|
+
* @param {number} scaleY - Масштаб по Y
|
|
219
|
+
* @private
|
|
220
|
+
*/
|
|
221
|
+
_drawPoints(pixiObject, points, scaleX, scaleY) {
|
|
222
|
+
if (points.length < 3) {
|
|
223
|
+
pixiObject.moveTo(points[0].x * scaleX, points[0].y * scaleY);
|
|
224
|
+
for (let i = 1; i < points.length; i++) {
|
|
225
|
+
pixiObject.lineTo(points[i].x * scaleX, points[i].y * scaleY);
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
pixiObject.moveTo(points[0].x * scaleX, points[0].y * scaleY);
|
|
229
|
+
for (let i = 1; i < points.length - 1; i++) {
|
|
230
|
+
const cx = points[i].x * scaleX, cy = points[i].y * scaleY;
|
|
231
|
+
const nx = points[i + 1].x * scaleX, ny = points[i + 1].y * scaleY;
|
|
232
|
+
const mx = (cx + nx) / 2, my = (cy + ny) / 2;
|
|
233
|
+
pixiObject.quadraticCurveTo(cx, cy, mx, my);
|
|
234
|
+
}
|
|
235
|
+
const pen = points[points.length - 2];
|
|
236
|
+
const last = points[points.length - 1];
|
|
237
|
+
pixiObject.quadraticCurveTo(pen.x * scaleX, pen.y * scaleY, last.x * scaleX, last.y * scaleY);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Перерисовать объект по умолчанию
|
|
243
|
+
* @param {PIXI.Graphics} pixiObject - PIXI Graphics объект
|
|
244
|
+
* @param {Object} size - Размер
|
|
245
|
+
* @private
|
|
246
|
+
*/
|
|
247
|
+
_redrawDefaultObject(pixiObject, size) {
|
|
248
|
+
const borderWidth = 2;
|
|
249
|
+
pixiObject.lineStyle(borderWidth, 0x333333, 1);
|
|
250
|
+
pixiObject.beginFill(0xFFFFFF, 0.1);
|
|
251
|
+
|
|
252
|
+
const halfBorder = borderWidth / 2;
|
|
253
|
+
pixiObject.drawRect(halfBorder, halfBorder, size.width - borderWidth, size.height - borderWidth);
|
|
254
|
+
pixiObject.endFill();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Обновить содержимое объекта
|
|
259
|
+
* @param {string} objectId - ID объекта
|
|
260
|
+
* @param {string} content - Новое содержимое
|
|
261
|
+
*/
|
|
262
|
+
updateObjectContent(objectId, content) {
|
|
263
|
+
const pixiObject = this.objects.get(objectId);
|
|
264
|
+
if (!pixiObject) return;
|
|
265
|
+
|
|
266
|
+
console.log(`🎨 Обновляем содержимое объекта ${objectId}:`, content);
|
|
267
|
+
|
|
268
|
+
// Делегируем изменение содержимого объекту
|
|
269
|
+
const meta = pixiObject._mb || {};
|
|
270
|
+
if (meta.instance) {
|
|
271
|
+
if (typeof meta.instance.setContent === 'function') {
|
|
272
|
+
meta.instance.setContent(content);
|
|
273
|
+
} else if (typeof meta.instance.setText === 'function') {
|
|
274
|
+
meta.instance.setText(content);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Скрыть текст объекта (используется во время редактирования)
|
|
281
|
+
* @param {string} objectId - ID объекта
|
|
282
|
+
*/
|
|
283
|
+
hideObjectText(objectId) {
|
|
284
|
+
const pixiObject = this.objects.get(objectId);
|
|
285
|
+
if (!pixiObject) return;
|
|
286
|
+
|
|
287
|
+
const meta = pixiObject._mb || {};
|
|
288
|
+
if (meta.instance && typeof meta.instance.hideText === 'function') {
|
|
289
|
+
meta.instance.hideText();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Показать текст объекта (используется после завершения редактирования)
|
|
295
|
+
* @param {string} objectId - ID объекта
|
|
296
|
+
*/
|
|
297
|
+
showObjectText(objectId) {
|
|
298
|
+
const pixiObject = this.objects.get(objectId);
|
|
299
|
+
if (!pixiObject) return;
|
|
300
|
+
|
|
301
|
+
const meta = pixiObject._mb || {};
|
|
302
|
+
if (meta.instance && typeof meta.instance.showText === 'function') {
|
|
303
|
+
meta.instance.showText();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Обновить угол поворота объекта
|
|
309
|
+
* @param {string} objectId - ID объекта
|
|
310
|
+
* @param {number} angleDegrees - Угол в градусах
|
|
311
|
+
*/
|
|
312
|
+
updateObjectRotation(objectId, angleDegrees) {
|
|
313
|
+
const pixiObject = this.objects.get(objectId);
|
|
314
|
+
if (!pixiObject) return;
|
|
315
|
+
|
|
316
|
+
const angleRadians = GeometryUtils.degreesToRadians(angleDegrees);
|
|
317
|
+
pixiObject.rotation = angleRadians;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Установить цвет заливки для фрейма
|
|
322
|
+
* @param {string} objectId - ID объекта
|
|
323
|
+
* @param {number} width - Ширина
|
|
324
|
+
* @param {number} height - Высота
|
|
325
|
+
* @param {number} fillColor - Цвет заливки
|
|
326
|
+
*/
|
|
327
|
+
setFrameFill(objectId, width, height, fillColor = 0xFFFFFF) {
|
|
328
|
+
const pixiObject = this.objects.get(objectId);
|
|
329
|
+
if (!pixiObject || !(pixiObject instanceof PIXI.Graphics)) return;
|
|
330
|
+
|
|
331
|
+
const meta = pixiObject._mb || {};
|
|
332
|
+
if (meta.type !== 'frame') return;
|
|
333
|
+
|
|
334
|
+
if (meta.instance) {
|
|
335
|
+
meta.instance.setFill(fillColor);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Получить объект по ID
|
|
341
|
+
* @param {string} objectId - ID объекта
|
|
342
|
+
* @returns {PIXI.DisplayObject|null} PIXI объект или null
|
|
343
|
+
*/
|
|
344
|
+
getObject(objectId) {
|
|
345
|
+
return this.objects.get(objectId) || null;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Получить все объекты
|
|
350
|
+
* @returns {Map} Map всех объектов
|
|
351
|
+
*/
|
|
352
|
+
getAllObjects() {
|
|
353
|
+
return this.objects;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Очистить все объекты
|
|
358
|
+
*/
|
|
359
|
+
clearAllObjects() {
|
|
360
|
+
// Слой очищается через PixiEngine, здесь только очищаем Map
|
|
361
|
+
this.objects.clear();
|
|
362
|
+
}
|
|
363
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import * as PIXI from 'pixi.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Основной рендерер PIXI приложения
|
|
5
|
+
* Отвечает за инициализацию PIXI и базовую настройку
|
|
6
|
+
*/
|
|
7
|
+
export class PixiRenderer {
|
|
8
|
+
constructor(container, options) {
|
|
9
|
+
this.container = container;
|
|
10
|
+
this.options = options;
|
|
11
|
+
this.app = null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Инициализировать PIXI приложение
|
|
16
|
+
* @returns {Promise<void>}
|
|
17
|
+
*/
|
|
18
|
+
async init() {
|
|
19
|
+
this.app = new PIXI.Application({
|
|
20
|
+
width: this.options.width,
|
|
21
|
+
height: this.options.height,
|
|
22
|
+
backgroundColor: this.options.backgroundColor,
|
|
23
|
+
antialias: true
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
this.container.appendChild(this.app.view);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Получить PIXI приложение
|
|
31
|
+
* @returns {PIXI.Application} PIXI приложение
|
|
32
|
+
*/
|
|
33
|
+
getApp() {
|
|
34
|
+
return this.app;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Получить главную сцену
|
|
39
|
+
* @returns {PIXI.Container} Главная сцена
|
|
40
|
+
*/
|
|
41
|
+
getStage() {
|
|
42
|
+
return this.app ? this.app.stage : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Получить view (canvas элемент)
|
|
47
|
+
* @returns {HTMLCanvasElement} Canvas элемент
|
|
48
|
+
*/
|
|
49
|
+
getView() {
|
|
50
|
+
return this.app ? this.app.view : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Изменить размер приложения
|
|
55
|
+
* @param {number} width - Новая ширина
|
|
56
|
+
* @param {number} height - Новая высота
|
|
57
|
+
*/
|
|
58
|
+
resize(width, height) {
|
|
59
|
+
if (this.app) {
|
|
60
|
+
this.app.renderer.resize(width, height);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Получить текущий размер
|
|
66
|
+
* @returns {{width: number, height: number}} Размер приложения
|
|
67
|
+
*/
|
|
68
|
+
getSize() {
|
|
69
|
+
if (this.app) {
|
|
70
|
+
return {
|
|
71
|
+
width: this.app.renderer.width,
|
|
72
|
+
height: this.app.renderer.height
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return { width: 0, height: 0 };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Установить цвет фона
|
|
80
|
+
* @param {number} color - Цвет фона
|
|
81
|
+
*/
|
|
82
|
+
setBackgroundColor(color) {
|
|
83
|
+
if (this.app) {
|
|
84
|
+
this.app.renderer.backgroundColor = color;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Получить цвет фона
|
|
90
|
+
* @returns {number} Цвет фона
|
|
91
|
+
*/
|
|
92
|
+
getBackgroundColor() {
|
|
93
|
+
return this.app ? this.app.renderer.backgroundColor : 0x000000;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Включить/выключить сглаживание
|
|
98
|
+
* @param {boolean} enabled - Включить сглаживание
|
|
99
|
+
*/
|
|
100
|
+
setAntialias(enabled) {
|
|
101
|
+
if (this.app) {
|
|
102
|
+
this.app.renderer.antialias = enabled;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Получить состояние сглаживания
|
|
108
|
+
* @returns {boolean} Включено ли сглаживание
|
|
109
|
+
*/
|
|
110
|
+
getAntialias() {
|
|
111
|
+
return this.app ? this.app.renderer.antialias : false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Очистить сцену
|
|
116
|
+
*/
|
|
117
|
+
clearStage() {
|
|
118
|
+
if (this.app && this.app.stage) {
|
|
119
|
+
this.app.stage.removeChildren();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Уничтожить приложение
|
|
125
|
+
*/
|
|
126
|
+
destroy() {
|
|
127
|
+
if (this.app) {
|
|
128
|
+
this.app.destroy(true);
|
|
129
|
+
this.app = null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Проверить, инициализировано ли приложение
|
|
135
|
+
* @returns {boolean} true если приложение инициализировано
|
|
136
|
+
*/
|
|
137
|
+
isInitialized() {
|
|
138
|
+
return this.app !== null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Экспорт всех классов рендеринга
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { PixiRenderer } from './PixiRenderer.js';
|
|
6
|
+
export { LayerManager } from './LayerManager.js';
|
|
7
|
+
export { ObjectRenderer } from './ObjectRenderer.js';
|
|
8
|
+
export { HitTestManager } from './HitTestManager.js';
|
|
9
|
+
export { GeometryUtils } from './GeometryUtils.js';
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import * as PIXI from 'pixi.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Базовый класс для всех типов сеток
|
|
5
|
+
*/
|
|
6
|
+
export class BaseGrid {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.type = 'base';
|
|
9
|
+
this.enabled = options.enabled ?? false;
|
|
10
|
+
this.size = options.size || 20;
|
|
11
|
+
this.color = options.color || 0xE0E0E0;
|
|
12
|
+
this.opacity = options.opacity || 0.5;
|
|
13
|
+
this.lineWidth = options.lineWidth || 1;
|
|
14
|
+
|
|
15
|
+
// Размеры области отрисовки
|
|
16
|
+
this.width = options.width || 1920;
|
|
17
|
+
this.height = options.height || 1080;
|
|
18
|
+
|
|
19
|
+
// PIXI графика
|
|
20
|
+
this.graphics = new PIXI.Graphics();
|
|
21
|
+
this.graphics.alpha = this.opacity;
|
|
22
|
+
|
|
23
|
+
// Настройки привязки
|
|
24
|
+
this.snapEnabled = options.snapEnabled ?? true;
|
|
25
|
+
this.snapTolerance = options.snapTolerance || 10;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Создает визуальное представление сетки
|
|
30
|
+
* Должен быть переопределен в дочерних классах
|
|
31
|
+
*/
|
|
32
|
+
createVisual() {
|
|
33
|
+
throw new Error('createVisual() должен быть реализован в дочернем классе');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Обновляет визуальное представление
|
|
38
|
+
*/
|
|
39
|
+
updateVisual() {
|
|
40
|
+
this.graphics.clear();
|
|
41
|
+
if (this.enabled) {
|
|
42
|
+
this.createVisual();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Привязывает точку к сетке
|
|
48
|
+
* @param {number} x - координата X
|
|
49
|
+
* @param {number} y - координата Y
|
|
50
|
+
* @returns {Object} - новые координаты {x, y}
|
|
51
|
+
*/
|
|
52
|
+
snapToGrid(x, y) {
|
|
53
|
+
if (!this.snapEnabled) {
|
|
54
|
+
return { x, y };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return this.calculateSnapPoint(x, y);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Вычисляет точку привязки к сетке
|
|
62
|
+
* Должен быть переопределен в дочерних классах
|
|
63
|
+
*/
|
|
64
|
+
calculateSnapPoint(x, y) {
|
|
65
|
+
return { x, y };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Находит ближайшие линии сетки
|
|
70
|
+
* @param {number} x - координата X
|
|
71
|
+
* @param {number} y - координата Y
|
|
72
|
+
* @returns {Object} - информация о ближайших линиях
|
|
73
|
+
*/
|
|
74
|
+
findNearestGridLines(x, y) {
|
|
75
|
+
return {
|
|
76
|
+
vertical: Math.round(x / this.size) * this.size,
|
|
77
|
+
horizontal: Math.round(y / this.size) * this.size,
|
|
78
|
+
distance: {
|
|
79
|
+
x: Math.abs(x % this.size),
|
|
80
|
+
y: Math.abs(y % this.size)
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Проверяет, находится ли точка в зоне привязки
|
|
87
|
+
*/
|
|
88
|
+
isInSnapZone(x, y) {
|
|
89
|
+
const nearest = this.findNearestGridLines(x, y);
|
|
90
|
+
return nearest.distance.x <= this.snapTolerance ||
|
|
91
|
+
nearest.distance.y <= this.snapTolerance;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Включает/выключает сетку
|
|
96
|
+
*/
|
|
97
|
+
setEnabled(enabled) {
|
|
98
|
+
this.enabled = enabled;
|
|
99
|
+
this.updateVisual();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Устанавливает размер сетки
|
|
104
|
+
*/
|
|
105
|
+
setSize(size) {
|
|
106
|
+
this.size = Math.max(1, size);
|
|
107
|
+
this.updateVisual();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Устанавливает цвет сетки
|
|
112
|
+
*/
|
|
113
|
+
setColor(color) {
|
|
114
|
+
this.color = color;
|
|
115
|
+
this.updateVisual();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Устанавливает прозрачность
|
|
120
|
+
*/
|
|
121
|
+
setOpacity(opacity) {
|
|
122
|
+
this.opacity = Math.max(0, Math.min(1, opacity));
|
|
123
|
+
this.graphics.alpha = this.opacity;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Изменяет размеры области отрисовки
|
|
128
|
+
*/
|
|
129
|
+
resize(width, height) {
|
|
130
|
+
this.width = width;
|
|
131
|
+
this.height = height;
|
|
132
|
+
this.updateVisual();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Возвращает PIXI объект для рендеринга
|
|
137
|
+
*/
|
|
138
|
+
getPixiObject() {
|
|
139
|
+
return this.graphics;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Сериализует настройки сетки
|
|
144
|
+
*/
|
|
145
|
+
serialize() {
|
|
146
|
+
return {
|
|
147
|
+
type: this.type,
|
|
148
|
+
enabled: this.enabled,
|
|
149
|
+
size: this.size,
|
|
150
|
+
color: this.color,
|
|
151
|
+
opacity: this.opacity,
|
|
152
|
+
lineWidth: this.lineWidth,
|
|
153
|
+
snapEnabled: this.snapEnabled,
|
|
154
|
+
snapTolerance: this.snapTolerance
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Очищает ресурсы
|
|
160
|
+
*/
|
|
161
|
+
destroy() {
|
|
162
|
+
this.graphics.destroy();
|
|
163
|
+
}
|
|
164
|
+
}
|