@elixpo/lixsketch 5.6.5 → 5.6.7
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/dist/mcp/index.js +5 -1
- package/dist/mcp/index.js.map +2 -2
- package/dist/mcp/stdio.js +5 -1
- package/dist/mcp/stdio.js.map +2 -2
- package/dist/react/{AIRenderer-SRF7LU5K.js → AIRenderer-R5M2CIDO.js} +13 -6
- package/dist/react/AIRenderer-R5M2CIDO.js.map +7 -0
- package/dist/react/Circle-HMM5PKJ2.js +10 -0
- package/dist/react/{CopyPaste-YUDZ6DHY.js → CopyPaste-PPDS3AD6.js} +11 -10
- package/dist/react/{CopyPaste-YUDZ6DHY.js.map → CopyPaste-PPDS3AD6.js.map} +1 -1
- package/dist/react/{EventDispatcher-7LAK7GQ7.js → EventDispatcher-UV7LFSAS.js} +216 -63
- package/dist/react/EventDispatcher-UV7LFSAS.js.map +7 -0
- package/dist/react/Frame-DELCMYV6.js +11 -0
- package/dist/react/{FreehandStroke-U6GYZOM4.js → FreehandStroke-WXIJKD5I.js} +2 -2
- package/dist/react/{GraphEngine-MM6ALA2N.js → GraphEngine-XH3RPYO4.js} +9 -2
- package/dist/react/GraphEngine-XH3RPYO4.js.map +7 -0
- package/dist/react/IconShape-PZHMZSLR.js +10 -0
- package/dist/react/ImageShape-ZMBZARRI.js +10 -0
- package/dist/react/Line-VPVPL6ET.js +10 -0
- package/dist/react/{LixScriptParser-JYWJX375.js → LixScriptParser-YS3ZOTGS.js} +6 -2
- package/dist/react/LixScriptParser-YS3ZOTGS.js.map +7 -0
- package/dist/react/{MermaidFlowchartRenderer-PQEC4TMI.js → MermaidFlowchartRenderer-EL3Q2MNW.js} +6 -2
- package/dist/react/MermaidFlowchartRenderer-EL3Q2MNW.js.map +7 -0
- package/dist/react/MermaidSequenceParser-DBYXW23P.js +8 -0
- package/dist/react/{MermaidSequenceRenderer-KCUHO7UI.js → MermaidSequenceRenderer-K4KHHEVC.js} +10 -3
- package/dist/react/MermaidSequenceRenderer-K4KHHEVC.js.map +7 -0
- package/dist/react/{MermaidStructuredRenderer-W7UKN7BM.js → MermaidStructuredRenderer-6LN3WISP.js} +7 -2
- package/dist/react/MermaidStructuredRenderer-6LN3WISP.js.map +7 -0
- package/dist/react/Rectangle-4U4CD2ER.js +10 -0
- package/dist/react/{SceneSerializer-SG6SJN5X.js → SceneSerializer-H6KMQCO2.js} +120 -46
- package/dist/react/SceneSerializer-H6KMQCO2.js.map +7 -0
- package/dist/react/{Selection-2TSH3J6P.js → Selection-CFWRGVKH.js} +4 -4
- package/dist/react/{SketchEngine-UD2XV2KT.js → SketchEngine-UFLZJQXS.js} +26 -26
- package/dist/react/{UndoRedo-IN4SXDG6.js → UndoRedo-RXO7YHA2.js} +12 -2
- package/dist/react/{arrowTool-OYDWJGQK.js → arrowTool-DO327FKU.js} +3 -3
- package/dist/react/{chunk-EOURRJ55.js → chunk-2ALS5JGS.js} +2 -2
- package/dist/react/{chunk-LJR7EVW2.js → chunk-37PF7FG2.js} +3 -3
- package/dist/react/chunk-6AIFXYV6.js +31 -0
- package/dist/react/chunk-6AIFXYV6.js.map +7 -0
- package/dist/react/{chunk-XOWNCYE4.js → chunk-6LWUR7XB.js} +3 -3
- package/dist/react/{chunk-ASBC5G35.js → chunk-7DD2JRBC.js} +2 -2
- package/dist/react/{chunk-OP56XPWF.js → chunk-BOZEDUAF.js} +2 -2
- package/dist/react/{chunk-RGMDWLRJ.js → chunk-CGMUXYPJ.js} +3 -3
- package/dist/react/{chunk-M6TMN76K.js → chunk-DODPOWNX.js} +3 -3
- package/dist/react/{chunk-LSHZYN77.js → chunk-GQLXUZHQ.js} +2 -2
- package/dist/react/{chunk-7CM5XQDN.js → chunk-I7TVGJFF.js} +20 -36
- package/dist/react/chunk-I7TVGJFF.js.map +7 -0
- package/dist/react/{chunk-K5RY4EV7.js → chunk-KC26KJW4.js} +26 -17
- package/dist/react/chunk-KC26KJW4.js.map +7 -0
- package/dist/react/{chunk-XFL4R5ZF.js → chunk-KZBKYHZO.js} +16 -35
- package/dist/react/chunk-KZBKYHZO.js.map +7 -0
- package/dist/react/{chunk-VQVEPJSA.js → chunk-MHV5P6TS.js} +2 -1
- package/dist/react/chunk-MHV5P6TS.js.map +7 -0
- package/dist/react/{chunk-PXFPPO3R.js → chunk-NH3NVSWT.js} +18 -2
- package/dist/react/chunk-NH3NVSWT.js.map +7 -0
- package/dist/react/{chunk-APTOPAOY.js → chunk-O7KXLFZN.js} +3 -3
- package/dist/react/{chunk-GQUZPON4.js → chunk-RK3NVAER.js} +4 -4
- package/dist/react/{chunk-FETTJLCV.js → chunk-VZPH7OAM.js} +14 -22
- package/dist/react/chunk-VZPH7OAM.js.map +7 -0
- package/dist/react/{chunk-6GPEWDRB.js → chunk-VZX55VQL.js} +115 -6
- package/dist/react/chunk-VZX55VQL.js.map +7 -0
- package/dist/react/{chunk-TKXKJNTX.js → chunk-W7XUY4GR.js} +16 -19
- package/dist/react/chunk-W7XUY4GR.js.map +7 -0
- package/dist/react/{chunk-XYUFEIZR.js → chunk-X5ENBMGL.js} +2 -2
- package/dist/react/{chunk-K5BG5MHY.js → chunk-ZNZAICWY.js} +36 -60
- package/dist/react/chunk-ZNZAICWY.js.map +7 -0
- package/dist/react/{circleTool-LDKGZALF.js → circleTool-NK4RVDAR.js} +4 -4
- package/dist/react/{codeTool-4YZO5W7J.js → codeTool-3WF4HEOW.js} +4 -4
- package/dist/react/{frameTool-MXNJEXQQ.js → frameTool-VCNV2NG2.js} +4 -4
- package/dist/react/{freehandTool-26D3FOOX.js → freehandTool-TBAOD46K.js} +4 -4
- package/dist/react/{iconTool-46O5VEUX.js → iconTool-65JCAKYF.js} +4 -4
- package/dist/react/{imageTool-P2RWZRHO.js → imageTool-UQZUBOK2.js} +4 -4
- package/dist/react/index.js +148 -37
- package/dist/react/index.js.map +4 -4
- package/dist/react/{lineTool-ZMR27U5L.js → lineTool-762KBYOM.js} +4 -4
- package/dist/react/{rectangleTool-EJKK6UTX.js → rectangleTool-IQK3HZK6.js} +4 -4
- package/dist/react/{textTool-S4XXZHZH.js → textTool-ITG2SBJK.js} +5 -5
- package/package.json +5 -2
- package/src/core/AIRenderer.js +8 -2
- package/src/core/CanvasSpace.js +28 -0
- package/src/core/GraphRenderer.js +8 -3
- package/src/core/LixScriptParser.js +7 -1
- package/src/core/MermaidFlowchartRenderer.js +5 -1
- package/src/core/MermaidSequenceParser.js +1 -0
- package/src/core/MermaidSequenceRenderer.js +8 -3
- package/src/core/MermaidStructuredRenderer.js +6 -1
- package/src/core/SceneSerializer.js +121 -38
- package/src/core/Selection.js +29 -14
- package/src/core/UndoRedo.js +126 -5
- package/src/react/components/AppMenu.jsx +20 -19
- package/src/react/components/canvas/CanvasRulers.jsx +8 -13
- package/src/react/components/canvas/SVGCanvas.jsx +2 -1
- package/src/react/components/canvas/rulerUnits.js +28 -0
- package/src/react/components/sidebars/PaintBucketSidebar.jsx +1 -1
- package/src/react/store/useSketchStore.js +24 -3
- package/src/react/store/useUIStore.js +24 -1
- package/src/react/utils/canvasBackgrounds.js +29 -0
- package/src/shapes/Circle.js +12 -19
- package/src/shapes/Frame.js +38 -69
- package/src/shapes/FreehandStroke.js +24 -1
- package/src/shapes/Rectangle.js +10 -22
- package/src/tools/circleTool.js +3 -27
- package/src/tools/paintBucketTool.js +72 -15
- package/src/tools/rectangleTool.js +7 -23
- package/src/tools/shapeRecognition.js +270 -0
- package/src/tools/shapeRecognitionTool.js +36 -183
- package/dist/react/AIRenderer-SRF7LU5K.js.map +0 -7
- package/dist/react/Circle-6POZCUZD.js +0 -9
- package/dist/react/EventDispatcher-7LAK7GQ7.js.map +0 -7
- package/dist/react/Frame-7D2MXCFF.js +0 -11
- package/dist/react/GraphEngine-MM6ALA2N.js.map +0 -7
- package/dist/react/IconShape-ZADX7Y33.js +0 -10
- package/dist/react/ImageShape-UJQN2HI7.js +0 -10
- package/dist/react/Line-APEULMHE.js +0 -10
- package/dist/react/LixScriptParser-JYWJX375.js.map +0 -7
- package/dist/react/MermaidFlowchartRenderer-PQEC4TMI.js.map +0 -7
- package/dist/react/MermaidSequenceParser-UKG2NSZY.js +0 -8
- package/dist/react/MermaidSequenceRenderer-KCUHO7UI.js.map +0 -7
- package/dist/react/MermaidStructuredRenderer-W7UKN7BM.js.map +0 -7
- package/dist/react/Rectangle-S7XUKWYD.js +0 -9
- package/dist/react/SceneSerializer-SG6SJN5X.js.map +0 -7
- package/dist/react/chunk-6GPEWDRB.js.map +0 -7
- package/dist/react/chunk-7CM5XQDN.js.map +0 -7
- package/dist/react/chunk-FETTJLCV.js.map +0 -7
- package/dist/react/chunk-K5BG5MHY.js.map +0 -7
- package/dist/react/chunk-K5RY4EV7.js.map +0 -7
- package/dist/react/chunk-PXFPPO3R.js.map +0 -7
- package/dist/react/chunk-TKXKJNTX.js.map +0 -7
- package/dist/react/chunk-VQVEPJSA.js.map +0 -7
- package/dist/react/chunk-XFL4R5ZF.js.map +0 -7
- /package/dist/react/{Circle-6POZCUZD.js.map → Circle-HMM5PKJ2.js.map} +0 -0
- /package/dist/react/{Frame-7D2MXCFF.js.map → Frame-DELCMYV6.js.map} +0 -0
- /package/dist/react/{FreehandStroke-U6GYZOM4.js.map → FreehandStroke-WXIJKD5I.js.map} +0 -0
- /package/dist/react/{IconShape-ZADX7Y33.js.map → IconShape-PZHMZSLR.js.map} +0 -0
- /package/dist/react/{ImageShape-UJQN2HI7.js.map → ImageShape-ZMBZARRI.js.map} +0 -0
- /package/dist/react/{Line-APEULMHE.js.map → Line-VPVPL6ET.js.map} +0 -0
- /package/dist/react/{MermaidSequenceParser-UKG2NSZY.js.map → MermaidSequenceParser-DBYXW23P.js.map} +0 -0
- /package/dist/react/{Rectangle-S7XUKWYD.js.map → Rectangle-4U4CD2ER.js.map} +0 -0
- /package/dist/react/{Selection-2TSH3J6P.js.map → Selection-CFWRGVKH.js.map} +0 -0
- /package/dist/react/{SketchEngine-UD2XV2KT.js.map → SketchEngine-UFLZJQXS.js.map} +0 -0
- /package/dist/react/{UndoRedo-IN4SXDG6.js.map → UndoRedo-RXO7YHA2.js.map} +0 -0
- /package/dist/react/{arrowTool-OYDWJGQK.js.map → arrowTool-DO327FKU.js.map} +0 -0
- /package/dist/react/{chunk-EOURRJ55.js.map → chunk-2ALS5JGS.js.map} +0 -0
- /package/dist/react/{chunk-LJR7EVW2.js.map → chunk-37PF7FG2.js.map} +0 -0
- /package/dist/react/{chunk-XOWNCYE4.js.map → chunk-6LWUR7XB.js.map} +0 -0
- /package/dist/react/{chunk-ASBC5G35.js.map → chunk-7DD2JRBC.js.map} +0 -0
- /package/dist/react/{chunk-OP56XPWF.js.map → chunk-BOZEDUAF.js.map} +0 -0
- /package/dist/react/{chunk-RGMDWLRJ.js.map → chunk-CGMUXYPJ.js.map} +0 -0
- /package/dist/react/{chunk-M6TMN76K.js.map → chunk-DODPOWNX.js.map} +0 -0
- /package/dist/react/{chunk-LSHZYN77.js.map → chunk-GQLXUZHQ.js.map} +0 -0
- /package/dist/react/{chunk-APTOPAOY.js.map → chunk-O7KXLFZN.js.map} +0 -0
- /package/dist/react/{chunk-GQUZPON4.js.map → chunk-RK3NVAER.js.map} +0 -0
- /package/dist/react/{chunk-XYUFEIZR.js.map → chunk-X5ENBMGL.js.map} +0 -0
- /package/dist/react/{circleTool-LDKGZALF.js.map → circleTool-NK4RVDAR.js.map} +0 -0
- /package/dist/react/{codeTool-4YZO5W7J.js.map → codeTool-3WF4HEOW.js.map} +0 -0
- /package/dist/react/{frameTool-MXNJEXQQ.js.map → frameTool-VCNV2NG2.js.map} +0 -0
- /package/dist/react/{freehandTool-26D3FOOX.js.map → freehandTool-TBAOD46K.js.map} +0 -0
- /package/dist/react/{iconTool-46O5VEUX.js.map → iconTool-65JCAKYF.js.map} +0 -0
- /package/dist/react/{imageTool-P2RWZRHO.js.map → imageTool-UQZUBOK2.js.map} +0 -0
- /package/dist/react/{lineTool-ZMR27U5L.js.map → lineTool-762KBYOM.js.map} +0 -0
- /package/dist/react/{rectangleTool-EJKK6UTX.js.map → rectangleTool-IQK3HZK6.js.map} +0 -0
- /package/dist/react/{textTool-S4XXZHZH.js.map → textTool-ITG2SBJK.js.map} +0 -0
package/src/shapes/Frame.js
CHANGED
|
@@ -101,6 +101,36 @@ class Frame {
|
|
|
101
101
|
let fillValue = "transparent";
|
|
102
102
|
if (this.fillStyle === 'solid') {
|
|
103
103
|
fillValue = this.fillColor;
|
|
104
|
+
} else if (['hachure', 'cross-hatch', 'dots'].includes(this.fillStyle)) {
|
|
105
|
+
const patternId = `frame-fill-${this.shapeID}`;
|
|
106
|
+
let pattern = defs.querySelector(`#${patternId}`);
|
|
107
|
+
if (!pattern) {
|
|
108
|
+
pattern = document.createElementNS('http://www.w3.org/2000/svg', 'pattern');
|
|
109
|
+
pattern.setAttribute('id', patternId);
|
|
110
|
+
pattern.setAttribute('patternUnits', 'userSpaceOnUse');
|
|
111
|
+
defs.appendChild(pattern);
|
|
112
|
+
}
|
|
113
|
+
pattern.setAttribute('width', 10);
|
|
114
|
+
pattern.setAttribute('height', 10);
|
|
115
|
+
while (pattern.firstChild) pattern.removeChild(pattern.firstChild);
|
|
116
|
+
if (this.fillStyle === 'dots') {
|
|
117
|
+
const dot = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
118
|
+
dot.setAttribute('cx', 5);
|
|
119
|
+
dot.setAttribute('cy', 5);
|
|
120
|
+
dot.setAttribute('r', 1.5);
|
|
121
|
+
dot.setAttribute('fill', this.fillColor);
|
|
122
|
+
pattern.appendChild(dot);
|
|
123
|
+
} else {
|
|
124
|
+
const hatch = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
125
|
+
hatch.setAttribute('d', this.fillStyle === 'cross-hatch'
|
|
126
|
+
? 'M-2 2L2-2M0 10L10 0M8 12L12 8M-2 8L2 12M0 0L10 10M8-2L12 2'
|
|
127
|
+
: 'M-2 2L2-2M0 10L10 0M8 12L12 8');
|
|
128
|
+
hatch.setAttribute('fill', 'none');
|
|
129
|
+
hatch.setAttribute('stroke', this.fillColor);
|
|
130
|
+
hatch.setAttribute('stroke-width', 1.5);
|
|
131
|
+
pattern.appendChild(hatch);
|
|
132
|
+
}
|
|
133
|
+
fillValue = `url(#${patternId})`;
|
|
104
134
|
} else if (this.fillStyle === 'grid') {
|
|
105
135
|
// Create or update a grid pattern unique to this frame
|
|
106
136
|
const patternId = `frame-grid-${this.shapeID}`;
|
|
@@ -426,6 +456,14 @@ move(dx, dy) {
|
|
|
426
456
|
this.updateClipPath();
|
|
427
457
|
}
|
|
428
458
|
|
|
459
|
+
finalizeMove() {
|
|
460
|
+
this.containedShapes.forEach(shape => {
|
|
461
|
+
if (shape && typeof shape.finalizeMove === 'function') {
|
|
462
|
+
shape.finalizeMove();
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
|
|
429
467
|
handleResizeLegacy(anchorIndex, currentPos, startPos, initialFrame) {
|
|
430
468
|
// Legacy version - superseded by the handleResize below with contained shapes scaling
|
|
431
469
|
const dx = currentPos.x - startPos.x;
|
|
@@ -1409,75 +1447,6 @@ restoreToFrame(shape) {
|
|
|
1409
1447
|
this.draw();
|
|
1410
1448
|
}
|
|
1411
1449
|
|
|
1412
|
-
destroy() {
|
|
1413
|
-
const isDiagramFrame = !!this._diagramType;
|
|
1414
|
-
|
|
1415
|
-
[...this.containedShapes].forEach(shape => {
|
|
1416
|
-
if (isDiagramFrame) {
|
|
1417
|
-
// Diagram/graph frame: destroy contained shapes with the frame
|
|
1418
|
-
if (typeof window.cleanupAttachments === 'function') {
|
|
1419
|
-
window.cleanupAttachments(shape);
|
|
1420
|
-
}
|
|
1421
|
-
const el = shape.group || shape.element;
|
|
1422
|
-
if (el && el.parentNode) {
|
|
1423
|
-
el.parentNode.removeChild(el);
|
|
1424
|
-
}
|
|
1425
|
-
if (shape.shapeName === 'frame') {
|
|
1426
|
-
if (shape.clipGroup && shape.clipGroup.parentNode) {
|
|
1427
|
-
shape.clipGroup.parentNode.removeChild(shape.clipGroup);
|
|
1428
|
-
}
|
|
1429
|
-
if (shape.clipPath && shape.clipPath.parentNode) {
|
|
1430
|
-
shape.clipPath.parentNode.removeChild(shape.clipPath);
|
|
1431
|
-
}
|
|
1432
|
-
}
|
|
1433
|
-
const idx = shapes.indexOf(shape);
|
|
1434
|
-
if (idx > -1) {
|
|
1435
|
-
shapes.splice(idx, 1);
|
|
1436
|
-
}
|
|
1437
|
-
} else {
|
|
1438
|
-
// Regular frame: release contained shapes back to canvas
|
|
1439
|
-
const el = shape.group || shape.element;
|
|
1440
|
-
if (el) {
|
|
1441
|
-
if (el.parentNode === this.clipGroup) {
|
|
1442
|
-
this.clipGroup.removeChild(el);
|
|
1443
|
-
}
|
|
1444
|
-
svg.appendChild(el);
|
|
1445
|
-
}
|
|
1446
|
-
if (shape.shapeName === 'frame' && shape.clipGroup) {
|
|
1447
|
-
if (shape.clipGroup.parentNode === this.clipGroup) {
|
|
1448
|
-
this.clipGroup.removeChild(shape.clipGroup);
|
|
1449
|
-
}
|
|
1450
|
-
svg.appendChild(shape.clipGroup);
|
|
1451
|
-
}
|
|
1452
|
-
}
|
|
1453
|
-
shape.parentFrame = null;
|
|
1454
|
-
delete shape.isBeingMovedByFrame;
|
|
1455
|
-
});
|
|
1456
|
-
this.containedShapes = [];
|
|
1457
|
-
|
|
1458
|
-
// Remove clip path
|
|
1459
|
-
if (this.clipPath && this.clipPath.parentNode) {
|
|
1460
|
-
this.clipPath.parentNode.removeChild(this.clipPath);
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
// Remove groups (clipGroup should be empty now)
|
|
1464
|
-
if (this.clipGroup && this.clipGroup.parentNode) {
|
|
1465
|
-
this.clipGroup.parentNode.removeChild(this.clipGroup);
|
|
1466
|
-
}
|
|
1467
|
-
|
|
1468
|
-
if (this.group && this.group.parentNode) {
|
|
1469
|
-
this.group.parentNode.removeChild(this.group);
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
const index = shapes.indexOf(this);
|
|
1473
|
-
if (index > -1) {
|
|
1474
|
-
shapes.splice(index, 1);
|
|
1475
|
-
}
|
|
1476
|
-
if (currentShape === this) {
|
|
1477
|
-
currentShape = null;
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
1450
|
/**
|
|
1482
1451
|
* Set a background image for this frame from a URL.
|
|
1483
1452
|
* The image will fit/cover the frame dimensions.
|
|
@@ -69,6 +69,8 @@ class FreehandStroke {
|
|
|
69
69
|
this.selectionOutline = null;
|
|
70
70
|
this.boundingBox = { x: 0, y: 0, width: 0, height: 0 };
|
|
71
71
|
this.shapeName = "freehandStroke";
|
|
72
|
+
this.shapeID = `freehand-${String(Date.now()).slice(0, 8)}-${Math.floor(Math.random() * 10000)}`;
|
|
73
|
+
this.group.setAttribute('id', this.shapeID);
|
|
72
74
|
this._moveOffsetX = 0;
|
|
73
75
|
this._moveOffsetY = 0;
|
|
74
76
|
|
|
@@ -304,7 +306,16 @@ class FreehandStroke {
|
|
|
304
306
|
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
305
307
|
const pathData = this.getPathData();
|
|
306
308
|
path.setAttribute('d', pathData);
|
|
307
|
-
|
|
309
|
+
const closedFillStyle = this.options.fillStyle || 'solid';
|
|
310
|
+
const closedFill = closedFillStyle === 'none'
|
|
311
|
+
? 'transparent'
|
|
312
|
+
: (this.options.fill || this.options.stroke);
|
|
313
|
+
const patternedFill = this.options.closedFill
|
|
314
|
+
&& !['none', 'solid'].includes(closedFillStyle)
|
|
315
|
+
&& typeof rough !== 'undefined';
|
|
316
|
+
path.setAttribute('fill', this.options.closedFill
|
|
317
|
+
? (patternedFill ? 'transparent' : closedFill)
|
|
318
|
+
: this.options.stroke);
|
|
308
319
|
path.setAttribute('fill-opacity', this.options.strokeOpacity);
|
|
309
320
|
path.setAttribute('stroke', this.options.closedFill ? (this.options.outlineStroke || 'none') : 'none');
|
|
310
321
|
if (this.options.closedFill) {
|
|
@@ -330,6 +341,18 @@ class FreehandStroke {
|
|
|
330
341
|
this.group.appendChild(overlay);
|
|
331
342
|
}
|
|
332
343
|
|
|
344
|
+
if (patternedFill) {
|
|
345
|
+
const fillGroup = rough.svg(svg).path(pathData, {
|
|
346
|
+
stroke: 'none',
|
|
347
|
+
fill: closedFill,
|
|
348
|
+
fillStyle: closedFillStyle,
|
|
349
|
+
fillWeight: Math.max(1, (this.options.strokeWidth || 2) / 2),
|
|
350
|
+
roughness: 0.8,
|
|
351
|
+
});
|
|
352
|
+
fillGroup.setAttribute('data-freehand-fill-pattern', closedFillStyle);
|
|
353
|
+
this.group.appendChild(fillGroup);
|
|
354
|
+
}
|
|
355
|
+
|
|
333
356
|
this.element = path;
|
|
334
357
|
this.group.appendChild(path);
|
|
335
358
|
|
package/src/shapes/Rectangle.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* eslint-disable */
|
|
2
2
|
import { registerRotationAnchor } from '../core/ScreenSpaceControls.js';
|
|
3
|
+
import { canvasToLocal, localToCanvas } from '../core/CanvasSpace.js';
|
|
3
4
|
// Rectangle shape class - extracted from drawSquare.js
|
|
4
5
|
// Depends on globals: svg, shapes, rough, currentShape, currentZoom, rc
|
|
5
6
|
|
|
@@ -640,18 +641,17 @@ class Rectangle {
|
|
|
640
641
|
// Helper to check if a point is near an anchor
|
|
641
642
|
isNearAnchor(x, y) {
|
|
642
643
|
if (!this.isSelected) return null;
|
|
643
|
-
const
|
|
644
|
-
const
|
|
644
|
+
const zoom = window.currentZoom || 1;
|
|
645
|
+
const buffer = 10 / zoom;
|
|
646
|
+
const anchorSize = 10 / zoom;
|
|
647
|
+
const transform = { x: this.x, y: this.y, rotation: this.rotation, centerX: this.width / 2, centerY: this.height / 2 };
|
|
645
648
|
|
|
646
649
|
// Iterate through anchors
|
|
647
650
|
for (let i = 0; i < this.anchors.length; i++) {
|
|
648
651
|
const anchor = this.anchors[i];
|
|
649
652
|
const anchorLocalX = parseFloat(anchor.getAttribute('x')) + anchorSize / 2;
|
|
650
653
|
const anchorLocalY = parseFloat(anchor.getAttribute('y')) + anchorSize / 2;
|
|
651
|
-
const
|
|
652
|
-
svgPoint.x = anchorLocalX;
|
|
653
|
-
svgPoint.y = anchorLocalY;
|
|
654
|
-
const transformedPoint = svgPoint.matrixTransform(this.group.getCTM());
|
|
654
|
+
const transformedPoint = localToCanvas({ x: anchorLocalX, y: anchorLocalY }, transform);
|
|
655
655
|
const anchorLeft = transformedPoint.x - anchorSize/2 - buffer;
|
|
656
656
|
const anchorRight = transformedPoint.x + anchorSize/2 + buffer;
|
|
657
657
|
const anchorTop = transformedPoint.y - anchorSize/2 - buffer;
|
|
@@ -665,10 +665,7 @@ class Rectangle {
|
|
|
665
665
|
if (this.rotationAnchor) {
|
|
666
666
|
const rotateAnchorLocalX = parseFloat(this.rotationAnchor.getAttribute('cx'));
|
|
667
667
|
const rotateAnchorLocalY = parseFloat(this.rotationAnchor.getAttribute('cy'));
|
|
668
|
-
const
|
|
669
|
-
svgPoint.x = rotateAnchorLocalX;
|
|
670
|
-
svgPoint.y = rotateAnchorLocalY;
|
|
671
|
-
const transformedPoint = svgPoint.matrixTransform(this.group.getCTM());
|
|
668
|
+
const transformedPoint = localToCanvas({ x: rotateAnchorLocalX, y: rotateAnchorLocalY }, transform);
|
|
672
669
|
const rotateAnchorRadius = parseFloat(this.rotationAnchor.getAttribute('r'));
|
|
673
670
|
const distSq = (x - transformedPoint.x)**2 + (y - transformedPoint.y)**2;
|
|
674
671
|
if (distSq <= (rotateAnchorRadius + buffer)**2) {
|
|
@@ -729,16 +726,10 @@ updateFrameContainment() {
|
|
|
729
726
|
}
|
|
730
727
|
|
|
731
728
|
updatePosition(anchorIndex, newMouseX, newMouseY) {
|
|
732
|
-
const CTM = this.group.getCTM();
|
|
733
|
-
if (!CTM) return;
|
|
734
|
-
const inverseCTM = CTM.inverse();
|
|
735
|
-
const svgPoint = svg.createSVGPoint();
|
|
736
|
-
svgPoint.x = newMouseX;
|
|
737
|
-
svgPoint.y = newMouseY;
|
|
738
|
-
const localMouse = svgPoint.matrixTransform(inverseCTM);
|
|
739
|
-
|
|
740
729
|
const oldW = this.width;
|
|
741
730
|
const oldH = this.height;
|
|
731
|
+
const transform = { x: this.x, y: this.y, rotation: this.rotation, centerX: oldW / 2, centerY: oldH / 2 };
|
|
732
|
+
const localMouse = canvasToLocal({ x: newMouseX, y: newMouseY }, transform);
|
|
742
733
|
|
|
743
734
|
// Determine fixed point in old local coords and compute raw new dimensions
|
|
744
735
|
let fixedOldX, fixedOldY, rawW, rawH;
|
|
@@ -754,10 +745,7 @@ updateFrameContainment() {
|
|
|
754
745
|
}
|
|
755
746
|
|
|
756
747
|
// Get world position of the fixed point using current CTM
|
|
757
|
-
const
|
|
758
|
-
fp.x = fixedOldX;
|
|
759
|
-
fp.y = fixedOldY;
|
|
760
|
-
const fixedWorld = fp.matrixTransform(CTM);
|
|
748
|
+
const fixedWorld = localToCanvas({ x: fixedOldX, y: fixedOldY }, transform);
|
|
761
749
|
|
|
762
750
|
// Handle negative dimensions (dragged past opposite edge)
|
|
763
751
|
const newW = Math.abs(rawW);
|
package/src/tools/circleTool.js
CHANGED
|
@@ -124,20 +124,8 @@ const handleMouseDown = (e) => {
|
|
|
124
124
|
else if(anchorInfo.type === 'rotate')
|
|
125
125
|
{
|
|
126
126
|
isRotatingShapeCircle = true;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
{
|
|
130
|
-
const svgPoint = svg.createSVGPoint();
|
|
131
|
-
svgPoint.x = currentShape.x;
|
|
132
|
-
svgPoint.y = currentShape.y;
|
|
133
|
-
const centerSVGPoint = svgPoint.matrixTransform(CTM);
|
|
134
|
-
startRotationMouseAngleCircle = Math.atan2(svgMouseY - centerSVGPoint.y, svgMouseX - centerSVGPoint.x) * (180 / Math.PI);
|
|
135
|
-
startShapeRotationCircle = currentShape.rotation;
|
|
136
|
-
}
|
|
137
|
-
else
|
|
138
|
-
{
|
|
139
|
-
isRotatingShapeCircle = false;
|
|
140
|
-
}
|
|
127
|
+
startRotationMouseAngleCircle = Math.atan2(svgMouseY - currentShape.y, svgMouseX - currentShape.x) * (180 / Math.PI);
|
|
128
|
+
startShapeRotationCircle = currentShape.rotation;
|
|
141
129
|
}
|
|
142
130
|
clickedOnShape = true;
|
|
143
131
|
}
|
|
@@ -293,13 +281,7 @@ const handleMouseMove = (e) => {
|
|
|
293
281
|
}
|
|
294
282
|
else if (isRotatingShapeCircle && currentShape && currentShape.isSelected)
|
|
295
283
|
{
|
|
296
|
-
const
|
|
297
|
-
if(CTM) {
|
|
298
|
-
const svgPoint = svg.createSVGPoint();
|
|
299
|
-
svgPoint.x = currentShape.x;
|
|
300
|
-
svgPoint.y = currentShape.y;
|
|
301
|
-
const centerSVGPoint = svgPoint.matrixTransform(CTM);
|
|
302
|
-
const currentMouseAngle = Math.atan2(svgMouseY - centerSVGPoint.y, svgMouseX - centerSVGPoint.x) * (180 / Math.PI);
|
|
284
|
+
const currentMouseAngle = Math.atan2(svgMouseY - currentShape.y, svgMouseX - currentShape.x) * (180 / Math.PI);
|
|
303
285
|
const angleDiff = currentMouseAngle - startRotationMouseAngleCircle;
|
|
304
286
|
let newRotation = startShapeRotationCircle + angleDiff;
|
|
305
287
|
const snapAngle = 15;
|
|
@@ -311,12 +293,6 @@ const handleMouseMove = (e) => {
|
|
|
311
293
|
currentShape.draw();
|
|
312
294
|
currentShape._skipAnchors = false;
|
|
313
295
|
svg.style.cursor = 'grabbing';
|
|
314
|
-
}
|
|
315
|
-
else
|
|
316
|
-
{
|
|
317
|
-
isRotatingShapeCircle = false;
|
|
318
|
-
svg.style.cursor = 'default';
|
|
319
|
-
}
|
|
320
296
|
}
|
|
321
297
|
else if (isSelectionToolActive && !isDrawingCircle && currentShape && currentShape.isSelected)
|
|
322
298
|
{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable */
|
|
2
2
|
import { pushOptionsChangeAction } from '../core/UndoRedo.js';
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const OPTION_FILL_SHAPES = new Set(['rectangle', 'circle']);
|
|
5
5
|
|
|
6
6
|
window.paintBucketSettings = window.paintBucketSettings || {
|
|
7
7
|
fillColor: '#A98DEB',
|
|
@@ -17,34 +17,91 @@ function getSVGPoint(event) {
|
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
function
|
|
20
|
+
function isClosedFreehand(shape) {
|
|
21
|
+
if (shape?.shapeName !== 'freehandStroke' || !Array.isArray(shape.points) || shape.points.length < 3) return false;
|
|
22
|
+
if (shape.options?.closedFill) return true;
|
|
23
|
+
const first = shape.points[0];
|
|
24
|
+
const last = shape.points[shape.points.length - 1];
|
|
25
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
26
|
+
for (const point of shape.points) {
|
|
27
|
+
const x = Number(point[0]) || 0;
|
|
28
|
+
const y = Number(point[1]) || 0;
|
|
29
|
+
minX = Math.min(minX, x);
|
|
30
|
+
minY = Math.min(minY, y);
|
|
31
|
+
maxX = Math.max(maxX, x);
|
|
32
|
+
maxY = Math.max(maxY, y);
|
|
33
|
+
}
|
|
34
|
+
const diagonal = Math.hypot(maxX - minX, maxY - minY);
|
|
35
|
+
const closeDistance = Math.hypot(last[0] - first[0], last[1] - first[1]);
|
|
36
|
+
const tolerance = Math.max((shape.options?.strokeWidth || 2) * 2, Math.min(16, diagonal * 0.08));
|
|
37
|
+
return diagonal > 0 && closeDistance <= tolerance;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function isPaintBucketFillable(shape) {
|
|
41
|
+
return OPTION_FILL_SHAPES.has(shape?.shapeName)
|
|
42
|
+
|| shape?.shapeName === 'frame'
|
|
43
|
+
|| isClosedFreehand(shape);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function findShapeAt(x, y) {
|
|
21
47
|
for (let index = shapes.length - 1; index >= 0; index -= 1) {
|
|
22
48
|
const shape = shapes[index];
|
|
23
|
-
if (
|
|
49
|
+
if (typeof shape?.contains !== 'function') continue;
|
|
24
50
|
if (shape.contains(x, y)) return shape;
|
|
25
51
|
}
|
|
26
52
|
return null;
|
|
27
53
|
}
|
|
28
54
|
|
|
29
|
-
export function
|
|
30
|
-
if (!
|
|
31
|
-
const { x, y } = getSVGPoint(event);
|
|
32
|
-
const shape = findClosedShapeAt(x, y);
|
|
33
|
-
if (!shape) {
|
|
34
|
-
window.dispatchEvent(new CustomEvent('lixsketch:bucket-miss'));
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const settings = window.paintBucketSettings;
|
|
55
|
+
export function fillShape(shape, settings = window.paintBucketSettings) {
|
|
56
|
+
if (!isPaintBucketFillable(shape)) return false;
|
|
39
57
|
const transparent = settings.fillStyle === 'none' || settings.fillStyle === 'transparent';
|
|
40
58
|
const nextFill = transparent ? 'transparent' : settings.fillColor;
|
|
41
59
|
const nextStyle = transparent ? 'none' : settings.fillStyle;
|
|
42
|
-
|
|
60
|
+
|
|
61
|
+
if (shape.shapeName === 'frame') {
|
|
62
|
+
if (shape.fillColor === nextFill && shape.fillStyle === nextStyle) return false;
|
|
63
|
+
const oldOptions = { ...shape.options, fillColor: shape.fillColor, fillStyle: shape.fillStyle };
|
|
64
|
+
const newOptions = { ...shape.options, fillColor: nextFill, fillStyle: nextStyle };
|
|
65
|
+
pushOptionsChangeAction(shape, oldOptions, newOptions);
|
|
66
|
+
shape.fillColor = nextFill;
|
|
67
|
+
shape.fillStyle = nextStyle;
|
|
68
|
+
shape.options = { ...shape.options, fillColor: nextFill, fillStyle: nextStyle };
|
|
69
|
+
shape.draw();
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const freehandOptions = shape.shapeName === 'freehandStroke'
|
|
74
|
+
? {
|
|
75
|
+
closedFill: true,
|
|
76
|
+
outlineStroke: shape.options.outlineStroke || shape.options.stroke,
|
|
77
|
+
}
|
|
78
|
+
: {};
|
|
79
|
+
if (shape.options.fill === nextFill
|
|
80
|
+
&& shape.options.fillStyle === nextStyle
|
|
81
|
+
&& Object.entries(freehandOptions).every(([key, value]) => shape.options[key] === value)) return false;
|
|
43
82
|
|
|
44
83
|
const oldOptions = { ...shape.options };
|
|
45
|
-
const newOptions = { ...shape.options, fill: nextFill, fillStyle: nextStyle };
|
|
84
|
+
const newOptions = { ...shape.options, ...freehandOptions, fill: nextFill, fillStyle: nextStyle };
|
|
46
85
|
pushOptionsChangeAction(shape, oldOptions, newOptions);
|
|
47
86
|
shape.options = newOptions;
|
|
48
87
|
shape.draw();
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function handlePaintBucketDown(event) {
|
|
92
|
+
if (!window.isPaintBucketToolActive || event.button !== 0) return;
|
|
93
|
+
const { x, y } = getSVGPoint(event);
|
|
94
|
+
const shape = findShapeAt(x, y);
|
|
95
|
+
if (!shape) {
|
|
96
|
+
window.dispatchEvent(new CustomEvent('lixsketch:bucket-miss'));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (!isPaintBucketFillable(shape)) {
|
|
100
|
+
window.dispatchEvent(new CustomEvent('lixsketch:bucket-not-fillable', {
|
|
101
|
+
detail: { shapeID: shape.shapeID, shapeType: shape.shapeName },
|
|
102
|
+
}));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (!fillShape(shape)) return;
|
|
49
106
|
window.dispatchEvent(new CustomEvent('lixsketch:bucket-filled', { detail: { shapeID: shape.shapeID } }));
|
|
50
107
|
}
|
|
@@ -113,18 +113,10 @@ const handleMouseDownRect = (e) => {
|
|
|
113
113
|
} else if (anchorInfo.type === 'rotate') {
|
|
114
114
|
isRotatingShapeSquare = true;
|
|
115
115
|
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
svgPoint.y = currentShape.height / 2;
|
|
121
|
-
const centerSVG = svgPoint.matrixTransform(CTM);
|
|
122
|
-
startRotationMouseAngleSquare = Math.atan2(mouseY - centerSVG.y, mouseX - centerSVG.x) * 180 / Math.PI;
|
|
123
|
-
startShapeRotationSquare = currentShape.rotation;
|
|
124
|
-
} else {
|
|
125
|
-
isRotatingShapeSquare = false;
|
|
126
|
-
console.warn("Could not get CTM for rotation.");
|
|
127
|
-
}
|
|
116
|
+
const centerX = currentShape.x + currentShape.width / 2;
|
|
117
|
+
const centerY = currentShape.y + currentShape.height / 2;
|
|
118
|
+
startRotationMouseAngleSquare = Math.atan2(mouseY - centerY, mouseX - centerX) * 180 / Math.PI;
|
|
119
|
+
startShapeRotationSquare = currentShape.rotation;
|
|
128
120
|
}
|
|
129
121
|
clickedOnShape = true;
|
|
130
122
|
} else if (currentShape.contains(mouseX, mouseY)) {
|
|
@@ -278,13 +270,9 @@ const handleMouseMoveRect = (e) => {
|
|
|
278
270
|
currentShape.draw();
|
|
279
271
|
currentShape._skipAnchors = false;
|
|
280
272
|
} else if (isRotatingShapeSquare && currentShape && currentShape.isSelected) {
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
svgPoint.x = currentShape.width / 2;
|
|
285
|
-
svgPoint.y = currentShape.height / 2;
|
|
286
|
-
const centerSVG = svgPoint.matrixTransform(CTM);
|
|
287
|
-
const currentRotationMouseAngle = Math.atan2(mouseY - centerSVG.y, mouseX - centerSVG.x) * 180 / Math.PI;
|
|
273
|
+
const centerX = currentShape.x + currentShape.width / 2;
|
|
274
|
+
const centerY = currentShape.y + currentShape.height / 2;
|
|
275
|
+
const currentRotationMouseAngle = Math.atan2(mouseY - centerY, mouseX - centerX) * 180 / Math.PI;
|
|
288
276
|
const angleDiff = currentRotationMouseAngle - startRotationMouseAngleSquare;
|
|
289
277
|
let newRotation = startShapeRotationSquare + angleDiff;
|
|
290
278
|
const snapAngle = 15;
|
|
@@ -296,10 +284,6 @@ const handleMouseMoveRect = (e) => {
|
|
|
296
284
|
currentShape.draw();
|
|
297
285
|
currentShape._skipAnchors = false;
|
|
298
286
|
svg.style.cursor = 'grabbing';
|
|
299
|
-
} else {
|
|
300
|
-
isRotatingShapeSquare = false;
|
|
301
|
-
svg.style.cursor = 'default';
|
|
302
|
-
}
|
|
303
287
|
} else if (isSelectionToolActive && !isDrawingSquare && currentShape && currentShape.isSelected) {
|
|
304
288
|
const anchorInfo = currentShape.isNearAnchor(mouseX, mouseY);
|
|
305
289
|
if (anchorInfo) {
|