@sequent-org/moodboard 1.4.49 → 1.4.51
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 +1 -1
- package/src/core/commands/ApplyCropCommand.js +90 -0
- package/src/core/commands/UpdateFramePropertiesCommand.js +9 -2
- package/src/core/flows/ObjectLifecycleFlow.js +25 -6
- package/src/objects/FrameObject.js +90 -8
- package/src/objects/ImageObject.js +23 -7
- package/src/services/CropController.js +242 -0
- package/src/services/FrameService.js +32 -0
- package/src/tools/manager/ToolManagerLifecycle.js +4 -0
- package/src/tools/object-tools/DrawingTool.js +1 -0
- package/src/tools/object-tools/selection/TransformInteractionController.js +50 -0
- package/src/ui/CropOverlay.js +654 -0
- package/src/ui/FramePropertiesPanel.js +1061 -407
- package/src/ui/HtmlHandlesLayer.js +2 -0
- package/src/ui/ImagePropertiesPanel.js +77 -41
- package/src/ui/connectors/ConnectionAnchorsLayer.js +4 -0
- package/src/ui/handles/HandlesDomRenderer.js +9 -2
- package/src/ui/handles/HandlesEventBridge.js +1 -0
- package/src/ui/styles/panels.css +362 -27
- package/src/ui/styles/workspace.css +21 -1
- package/src/utils/applyCrop.js +92 -0
|
@@ -929,7 +929,7 @@
|
|
|
929
929
|
.moodboard-root, :root {
|
|
930
930
|
/* CSS variables for frame style */
|
|
931
931
|
--frame-border-color: #C7CDD6;
|
|
932
|
-
--frame-border-width:
|
|
932
|
+
--frame-border-width: 5; /* px */
|
|
933
933
|
--frame-corner-radius: 6; /* px */
|
|
934
934
|
}
|
|
935
935
|
.frame-popup {
|
|
@@ -1622,3 +1622,23 @@
|
|
|
1622
1622
|
}
|
|
1623
1623
|
}
|
|
1624
1624
|
|
|
1625
|
+
/* ── Crop Overlay ─────────────────────────────────────────────────────────── */
|
|
1626
|
+
|
|
1627
|
+
.mb-crop-overlay {
|
|
1628
|
+
position: absolute;
|
|
1629
|
+
inset: 0;
|
|
1630
|
+
z-index: 2000;
|
|
1631
|
+
user-select: none;
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
.mb-crop-img-area {
|
|
1635
|
+
position: absolute;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
/* Стили ручек задаются инлайн в CropOverlay.js (уголки и засечки) */
|
|
1639
|
+
.mb-crop-handle {
|
|
1640
|
+
position: absolute;
|
|
1641
|
+
box-sizing: border-box;
|
|
1642
|
+
z-index: 1;
|
|
1643
|
+
}
|
|
1644
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as PIXI from 'pixi.js';
|
|
2
|
+
import { applyRoundedMask } from './applyRoundedMask.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Применяет или снимает кроп на PIXI-спрайте изображения.
|
|
6
|
+
*
|
|
7
|
+
* @param {PIXI.Sprite} sprite
|
|
8
|
+
* @param {object|null} cropRect - { x, y, w, h } нормализованные 0..1 от оригинальной текстуры, или null
|
|
9
|
+
* @param {string|null} cropShape - 'circle' | 'rect' | null
|
|
10
|
+
* @param {{ width: number, height: number }} displaySize - размер отображения в мировых пикселях
|
|
11
|
+
* @param {{ x: number, y: number }} position - top-left позиция в мировых координатах
|
|
12
|
+
* @param {number} [borderRadius=0] - радиус скругления (переприменяем после снятия кропа)
|
|
13
|
+
*/
|
|
14
|
+
export function applyCropToSprite(sprite, cropRect, cropShape, displaySize, position, borderRadius = 0) {
|
|
15
|
+
if (!sprite || !sprite.texture?.baseTexture) return;
|
|
16
|
+
|
|
17
|
+
const baseTex = sprite.texture.baseTexture;
|
|
18
|
+
// Оригинальные размеры текстуры (до любого кропа)
|
|
19
|
+
const baseW = sprite._mb?.properties?.baseW || baseTex.realWidth || baseTex.width || 1;
|
|
20
|
+
const baseH = sprite._mb?.properties?.baseH || baseTex.realHeight || baseTex.height || 1;
|
|
21
|
+
const src = sprite._mb?.properties?.src;
|
|
22
|
+
|
|
23
|
+
// Снять circle-маску кропа
|
|
24
|
+
if (sprite._cropCircleMask) {
|
|
25
|
+
if (sprite.mask === sprite._cropCircleMask) sprite.mask = null;
|
|
26
|
+
try { sprite._cropCircleMask.destroy({ children: true }); } catch (_) {}
|
|
27
|
+
sprite._cropCircleMask = null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const { width: dW, height: dH } = displaySize;
|
|
31
|
+
|
|
32
|
+
if (cropRect) {
|
|
33
|
+
const { x: nx, y: ny, w: nw, h: nh } = cropRect;
|
|
34
|
+
const frameX = Math.max(0, Math.round(nx * baseW));
|
|
35
|
+
const frameY = Math.max(0, Math.round(ny * baseH));
|
|
36
|
+
const frameW = Math.max(1, Math.min(Math.round(nw * baseW), baseW - frameX));
|
|
37
|
+
const frameH = Math.max(1, Math.min(Math.round(nh * baseH), baseH - frameY));
|
|
38
|
+
|
|
39
|
+
// Новая текстура с кадрированием
|
|
40
|
+
const croppedTex = new PIXI.Texture(baseTex, new PIXI.Rectangle(frameX, frameY, frameW, frameH));
|
|
41
|
+
sprite.texture = croppedTex;
|
|
42
|
+
sprite.scale.set(dW / frameW, dH / frameH);
|
|
43
|
+
sprite.x = Math.round(position.x + dW / 2);
|
|
44
|
+
sprite.y = Math.round(position.y + dH / 2);
|
|
45
|
+
|
|
46
|
+
// Сбросить borderRadius-маску — circle или radius переустановит её
|
|
47
|
+
if (sprite._borderRadiusMask) {
|
|
48
|
+
if (sprite.mask === sprite._borderRadiusMask) sprite.mask = null;
|
|
49
|
+
try { sprite._borderRadiusMask.destroy({ children: true }); } catch (_) {}
|
|
50
|
+
sprite._borderRadiusMask = null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (cropShape === 'circle') {
|
|
54
|
+
// Вписанная окружность в локальных координатах спрайта (local = texture frame px)
|
|
55
|
+
const r = Math.min(frameW, frameH) / 2;
|
|
56
|
+
const g = new PIXI.Graphics();
|
|
57
|
+
g.beginFill(0xffffff);
|
|
58
|
+
g.drawCircle(0, 0, r);
|
|
59
|
+
g.endFill();
|
|
60
|
+
sprite.addChild(g);
|
|
61
|
+
sprite._cropCircleMask = g;
|
|
62
|
+
sprite.mask = g;
|
|
63
|
+
} else if (borderRadius > 0) {
|
|
64
|
+
applyRoundedMask(sprite, borderRadius);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
} else {
|
|
68
|
+
// Восстановить оригинальную текстуру
|
|
69
|
+
if (src) {
|
|
70
|
+
try {
|
|
71
|
+
const origTex = PIXI.Texture.from(src);
|
|
72
|
+
if (origTex) sprite.texture = origTex;
|
|
73
|
+
} catch (_) {}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const texW = sprite.texture.width || 1;
|
|
77
|
+
const texH = sprite.texture.height || 1;
|
|
78
|
+
sprite.scale.set(dW / texW, dH / texH);
|
|
79
|
+
sprite.x = Math.round(position.x + dW / 2);
|
|
80
|
+
sprite.y = Math.round(position.y + dH / 2);
|
|
81
|
+
|
|
82
|
+
// Сбросить borderRadius-маску и переприменить если нужно
|
|
83
|
+
if (sprite._borderRadiusMask) {
|
|
84
|
+
if (sprite.mask === sprite._borderRadiusMask) sprite.mask = null;
|
|
85
|
+
try { sprite._borderRadiusMask.destroy({ children: true }); } catch (_) {}
|
|
86
|
+
sprite._borderRadiusMask = null;
|
|
87
|
+
}
|
|
88
|
+
if (borderRadius > 0) {
|
|
89
|
+
applyRoundedMask(sprite, borderRadius);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|