@tsdraw/core 0.7.0 → 0.8.1

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/index.cjs CHANGED
@@ -225,25 +225,34 @@ var StateNode = class {
225
225
 
226
226
  // src/canvas/viewport.ts
227
227
  function createViewport() {
228
- return { x: 0, y: 0, zoom: 1 };
228
+ return { x: 0, y: 0, zoom: 1, rotation: 0 };
229
229
  }
230
230
  function screenToPage(viewport, screenX, screenY) {
231
+ const tx = screenX - viewport.x;
232
+ const ty = screenY - viewport.y;
233
+ const cos = Math.cos(viewport.rotation);
234
+ const sin = Math.sin(viewport.rotation);
231
235
  return {
232
- x: (screenX - viewport.x) / viewport.zoom,
233
- y: (screenY - viewport.y) / viewport.zoom
236
+ x: (tx * cos + ty * sin) / viewport.zoom,
237
+ y: (-tx * sin + ty * cos) / viewport.zoom
234
238
  };
235
239
  }
236
240
  function pageToScreen(viewport, pageX, pageY) {
241
+ const scaledX = pageX * viewport.zoom;
242
+ const scaledY = pageY * viewport.zoom;
243
+ const cos = Math.cos(viewport.rotation);
244
+ const sin = Math.sin(viewport.rotation);
237
245
  return {
238
- x: pageX * viewport.zoom + viewport.x,
239
- y: pageY * viewport.zoom + viewport.y
246
+ x: scaledX * cos - scaledY * sin + viewport.x,
247
+ y: scaledX * sin + scaledY * cos + viewport.y
240
248
  };
241
249
  }
242
250
  function setViewport(viewport, updater) {
243
251
  return {
244
252
  x: updater.x ?? viewport.x,
245
253
  y: updater.y ?? viewport.y,
246
- zoom: updater.zoom ?? viewport.zoom
254
+ zoom: updater.zoom ?? viewport.zoom,
255
+ rotation: updater.rotation ?? viewport.rotation
247
256
  };
248
257
  }
249
258
  function panViewport(viewport, dx, dy) {
@@ -255,9 +264,23 @@ function zoomViewport(viewport, factor, centerX, centerY) {
255
264
  return { ...viewport, zoom };
256
265
  }
257
266
  const pageBefore = screenToPage(viewport, centerX, centerY);
258
- const x = centerX - pageBefore.x * zoom;
259
- const y = centerY - pageBefore.y * zoom;
260
- return { x, y, zoom };
267
+ const cos = Math.cos(viewport.rotation);
268
+ const sin = Math.sin(viewport.rotation);
269
+ const x = centerX - (pageBefore.x * zoom * cos - pageBefore.y * zoom * sin);
270
+ const y = centerY - (pageBefore.x * zoom * sin + pageBefore.y * zoom * cos);
271
+ return { x, y, zoom, rotation: viewport.rotation };
272
+ }
273
+ function rotateViewport(viewport, delta, centerX, centerY) {
274
+ const rotation = viewport.rotation + delta;
275
+ if (centerX == null || centerY == null) {
276
+ return { ...viewport, rotation };
277
+ }
278
+ const pageBefore = screenToPage(viewport, centerX, centerY);
279
+ const cos = Math.cos(rotation);
280
+ const sin = Math.sin(rotation);
281
+ const x = centerX - (pageBefore.x * viewport.zoom * cos - pageBefore.y * viewport.zoom * sin);
282
+ const y = centerY - (pageBefore.x * viewport.zoom * sin + pageBefore.y * viewport.zoom * cos);
283
+ return { x, y, zoom: viewport.zoom, rotation };
261
284
  }
262
285
 
263
286
  // src/utils/colors.ts
@@ -290,6 +313,7 @@ var CanvasRenderer = class {
290
313
  render(ctx, viewport, shapes) {
291
314
  ctx.save();
292
315
  ctx.translate(viewport.x, viewport.y);
316
+ ctx.rotate(viewport.rotation);
293
317
  ctx.scale(viewport.zoom, viewport.zoom);
294
318
  for (const shape of shapes) {
295
319
  if (shape.type === "draw") {
@@ -450,9 +474,6 @@ function flattenSegments(shape) {
450
474
  }
451
475
  out.push(D);
452
476
  }
453
- if (out.length > 0 && !shape.props.isPen) {
454
- for (const p of out) p.pressure = 0.5;
455
- }
456
477
  return out;
457
478
  }
458
479
  function getLineDash(dash, width) {
@@ -582,6 +603,7 @@ var ToolManager = class {
582
603
  getCurrentState() {
583
604
  return this.currentState;
584
605
  }
606
+ // Transition between states within the same tool (ex. pen_idle -> pen_drawing)
585
607
  transition(stateId, info) {
586
608
  const next = this.states.get(stateId);
587
609
  if (!next) return;
@@ -783,8 +805,8 @@ var PenDrawingState = class extends StateNode {
783
805
  const penActive = inputs.getIsPen();
784
806
  const z = this._startInfo?.point?.z ?? 0.5;
785
807
  this._isPenDevice = penActive;
786
- this._hasPressure = penActive && z !== 0 || z > 0 && z < 0.5 || z > 0.5 && z < 1;
787
- const pressure = this._hasPressure ? toFixed(z * 1.25) : 0.5;
808
+ this._hasPressure = penActive || z !== 0.5;
809
+ const pressure = this._hasPressure ? toFixed(z) : 0.5;
788
810
  this._phase = inputs.getShiftKey() ? "straight" : "free";
789
811
  this._extending = false;
790
812
  this._lastSample = { ...origin };
@@ -879,8 +901,15 @@ var PenDrawingState = class extends StateNode {
879
901
  const { id, props: { size, scale } } = target;
880
902
  const { segments } = shape.props;
881
903
  const curPt = inputs.getCurrentPagePoint();
904
+ if (!this._hasPressure) {
905
+ const liveZ = curPt.z ?? 0.5;
906
+ if (liveZ !== 0.5 || inputs.getIsPen()) {
907
+ this._hasPressure = true;
908
+ this.editor.updateShapes([{ id, type: "draw", props: { isPen: true } }]);
909
+ }
910
+ }
882
911
  const local = this.editor.getPointInShapeSpace(shape, curPt);
883
- const pressure = this._hasPressure ? toFixed((curPt.z ?? 0.5) * 1.25) : 0.5;
912
+ const pressure = this._hasPressure ? toFixed(curPt.z ?? 0.5) : 0.5;
884
913
  const pt = { x: toFixed(local.x), y: toFixed(local.y), z: pressure };
885
914
  switch (this._phase) {
886
915
  case "starting_straight": {
@@ -1026,7 +1055,7 @@ var PenDrawingState = class extends StateNode {
1026
1055
  const firstPt = {
1027
1056
  x: 0,
1028
1057
  y: 0,
1029
- z: this._hasPressure ? toFixed((curPage.z ?? 0.5) * 1.25) : 0.5
1058
+ z: this._hasPressure ? toFixed(curPage.z ?? 0.5) : 0.5
1030
1059
  };
1031
1060
  this._activePts = [firstPt];
1032
1061
  this.editor.createShape({
@@ -1101,6 +1130,7 @@ var GeometricDrawingState = class extends StateNode {
1101
1130
  });
1102
1131
  this.currentShapeId = nextShapeId;
1103
1132
  }
1133
+ // Shift key switches between constrained and unconstrained bounds
1104
1134
  onPointerMove() {
1105
1135
  const activeShape = this.getActiveShape();
1106
1136
  if (!activeShape) return;
@@ -1134,6 +1164,8 @@ var GeometricDrawingState = class extends StateNode {
1134
1164
  onKeyUp() {
1135
1165
  this.onPointerMove();
1136
1166
  }
1167
+ // If user dragged, use the drag extents for the final shape
1168
+ // If they just clicked without dragging, use default-sized shape
1137
1169
  completeShape() {
1138
1170
  const activeShape = this.getActiveShape();
1139
1171
  const config = this.getConfig();
@@ -1454,6 +1486,8 @@ var EraserErasingState = class extends StateNode {
1454
1486
  onCancel() {
1455
1487
  this.ctx.transition("eraser_idle");
1456
1488
  }
1489
+ // On every pointer move, test the line from previous pointer position to current one against nearby shapes
1490
+ // Only select shapes whose bounding box overlaps the sweep area to avoid testing all shapes
1457
1491
  sweep() {
1458
1492
  const zoom = this.editor.getZoomLevel();
1459
1493
  const tolerance = ERASER_MARGIN / zoom;
@@ -1472,6 +1506,7 @@ var EraserErasingState = class extends StateNode {
1472
1506
  this._marked = [...hitIds];
1473
1507
  this.editor.setErasingShapes(this._marked);
1474
1508
  }
1509
+ // Delete marked shapes and reset, then go back to idle
1475
1510
  finish() {
1476
1511
  const ids = this.editor.getErasingShapeIds();
1477
1512
  if (ids.length > 0) {
@@ -1736,7 +1771,8 @@ var Editor = class {
1736
1771
  this.viewport = {
1737
1772
  x: partial.x ?? this.viewport.x,
1738
1773
  y: partial.y ?? this.viewport.y,
1739
- zoom: Math.max(0.1, Math.min(4, rawZoom))
1774
+ zoom: Math.max(0.1, Math.min(4, rawZoom)),
1775
+ rotation: partial.rotation ?? this.viewport.rotation
1740
1776
  };
1741
1777
  this.emitChange();
1742
1778
  }
@@ -1746,6 +1782,18 @@ var Editor = class {
1746
1782
  y: this.viewport.y + dy
1747
1783
  });
1748
1784
  }
1785
+ zoomAt(factor, screenX, screenY) {
1786
+ this.viewport = zoomViewport(this.viewport, factor, screenX, screenY);
1787
+ this.emitChange();
1788
+ }
1789
+ rotateAt(delta, screenX, screenY) {
1790
+ this.viewport = rotateViewport(this.viewport, delta, screenX, screenY);
1791
+ this.emitChange();
1792
+ }
1793
+ deleteShapes(ids) {
1794
+ if (ids.length === 0) return;
1795
+ this.store.deleteShapes(ids);
1796
+ }
1749
1797
  getDocumentSnapshot() {
1750
1798
  return {
1751
1799
  records: documentSnapshotToRecords(this.store.getSnapshot())
@@ -1764,7 +1812,8 @@ var Editor = class {
1764
1812
  viewport: {
1765
1813
  x: this.viewport.x,
1766
1814
  y: this.viewport.y,
1767
- zoom: this.viewport.zoom
1815
+ zoom: this.viewport.zoom,
1816
+ rotation: this.viewport.rotation
1768
1817
  },
1769
1818
  currentToolId: this.getCurrentToolId(),
1770
1819
  drawStyle: this.getCurrentDrawStyle(),
@@ -1772,7 +1821,10 @@ var Editor = class {
1772
1821
  };
1773
1822
  }
1774
1823
  loadSessionStateSnapshot(snapshot) {
1775
- this.setViewport(snapshot.viewport);
1824
+ this.setViewport({
1825
+ ...snapshot.viewport,
1826
+ rotation: snapshot.viewport.rotation ?? 0
1827
+ });
1776
1828
  this.setCurrentDrawStyle({
1777
1829
  color: snapshot.drawStyle.color,
1778
1830
  dash: snapshot.drawStyle.dash,
@@ -2213,6 +2265,7 @@ exports.pointHitsShape = pointHitsShape;
2213
2265
  exports.recordsToDocumentSnapshot = recordsToDocumentSnapshot;
2214
2266
  exports.resolveThemeColor = resolveThemeColor;
2215
2267
  exports.rotatePoint = rotatePoint;
2268
+ exports.rotateViewport = rotateViewport;
2216
2269
  exports.screenToPage = screenToPage;
2217
2270
  exports.segmentHitsShape = segmentHitsShape;
2218
2271
  exports.segmentTouchesPolyline = segmentTouchesPolyline;