@tsdraw/core 0.8.2 → 0.8.4

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,34 +225,25 @@ var StateNode = class {
225
225
 
226
226
  // src/canvas/viewport.ts
227
227
  function createViewport() {
228
- return { x: 0, y: 0, zoom: 1, rotation: 0 };
228
+ return { x: 0, y: 0, zoom: 1 };
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);
235
231
  return {
236
- x: (tx * cos + ty * sin) / viewport.zoom,
237
- y: (-tx * sin + ty * cos) / viewport.zoom
232
+ x: (screenX - viewport.x) / viewport.zoom,
233
+ y: (screenY - viewport.y) / viewport.zoom
238
234
  };
239
235
  }
240
236
  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);
245
237
  return {
246
- x: scaledX * cos - scaledY * sin + viewport.x,
247
- y: scaledX * sin + scaledY * cos + viewport.y
238
+ x: pageX * viewport.zoom + viewport.x,
239
+ y: pageY * viewport.zoom + viewport.y
248
240
  };
249
241
  }
250
242
  function setViewport(viewport, updater) {
251
243
  return {
252
244
  x: updater.x ?? viewport.x,
253
245
  y: updater.y ?? viewport.y,
254
- zoom: updater.zoom ?? viewport.zoom,
255
- rotation: updater.rotation ?? viewport.rotation
246
+ zoom: updater.zoom ?? viewport.zoom
256
247
  };
257
248
  }
258
249
  function panViewport(viewport, dx, dy) {
@@ -264,23 +255,92 @@ function zoomViewport(viewport, factor, centerX, centerY) {
264
255
  return { ...viewport, zoom };
265
256
  }
266
257
  const pageBefore = screenToPage(viewport, centerX, centerY);
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 };
258
+ const x = centerX - pageBefore.x * zoom;
259
+ const y = centerY - pageBefore.y * zoom;
260
+ return { x, y, zoom };
261
+ }
262
+
263
+ // src/canvas/cameraPan.ts
264
+ var VELOCITY_LERP_FACTOR = 0.5;
265
+ var VELOCITY_ZERO_THRESHOLD = 0.01;
266
+ function beginCameraPan(viewport, screenX, screenY) {
267
+ return {
268
+ initialViewportX: viewport.x,
269
+ initialViewportY: viewport.y,
270
+ originScreenX: screenX,
271
+ originScreenY: screenY,
272
+ velocityX: 0,
273
+ velocityY: 0,
274
+ previousScreenX: screenX,
275
+ previousScreenY: screenY,
276
+ lastMoveTime: performance.now()
277
+ };
278
+ }
279
+ function moveCameraPan(session, currentScreenX, currentScreenY) {
280
+ const now = performance.now();
281
+ const elapsed = now - session.lastMoveTime;
282
+ if (elapsed > 0) {
283
+ const moveDx = currentScreenX - session.previousScreenX;
284
+ const moveDy = currentScreenY - session.previousScreenY;
285
+ const moveLen = Math.hypot(moveDx, moveDy);
286
+ if (moveLen > 0) {
287
+ const dirX = moveDx / moveLen;
288
+ const dirY = moveDy / moveLen;
289
+ const speed = moveLen / elapsed;
290
+ session.velocityX += (dirX * speed - session.velocityX) * VELOCITY_LERP_FACTOR;
291
+ session.velocityY += (dirY * speed - session.velocityY) * VELOCITY_LERP_FACTOR;
292
+ }
293
+ if (Math.abs(session.velocityX) < VELOCITY_ZERO_THRESHOLD) session.velocityX = 0;
294
+ if (Math.abs(session.velocityY) < VELOCITY_ZERO_THRESHOLD) session.velocityY = 0;
277
295
  }
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 };
296
+ session.previousScreenX = currentScreenX;
297
+ session.previousScreenY = currentScreenY;
298
+ session.lastMoveTime = now;
299
+ return {
300
+ x: session.initialViewportX + (currentScreenX - session.originScreenX),
301
+ y: session.initialViewportY + (currentScreenY - session.originScreenY)
302
+ };
303
+ }
304
+ var SLIDE_FRICTION = 0.92;
305
+ var SLIDE_MIN_SPEED = 0.01;
306
+ var SLIDE_MAX_SPEED = 2;
307
+ var SLIDE_MIN_VELOCITY_TO_START = 0.1;
308
+ function startCameraSlide(session, applyPan, onFrame) {
309
+ const timeSinceLastMove = performance.now() - session.lastMoveTime;
310
+ const FRAME_DURATION = 16;
311
+ const decayFactor = Math.pow(1 - VELOCITY_LERP_FACTOR, timeSinceLastMove / FRAME_DURATION);
312
+ const effectiveVx = session.velocityX * decayFactor;
313
+ const effectiveVy = session.velocityY * decayFactor;
314
+ const speed = Math.hypot(effectiveVx, effectiveVy);
315
+ const clampedSpeed = Math.min(speed, SLIDE_MAX_SPEED);
316
+ if (clampedSpeed < SLIDE_MIN_VELOCITY_TO_START) return null;
317
+ const dirX = effectiveVx / speed;
318
+ const dirY = effectiveVy / speed;
319
+ let currentSpeed = clampedSpeed;
320
+ let lastTime = performance.now();
321
+ let rafId = 0;
322
+ const tick = () => {
323
+ const now = performance.now();
324
+ const elapsed = now - lastTime;
325
+ lastTime = now;
326
+ applyPan(dirX * currentSpeed * elapsed, dirY * currentSpeed * elapsed);
327
+ onFrame();
328
+ currentSpeed *= SLIDE_FRICTION;
329
+ if (currentSpeed < SLIDE_MIN_SPEED) {
330
+ rafId = 0;
331
+ return;
332
+ }
333
+ rafId = requestAnimationFrame(tick);
334
+ };
335
+ rafId = requestAnimationFrame(tick);
336
+ return {
337
+ stop() {
338
+ if (rafId !== 0) {
339
+ cancelAnimationFrame(rafId);
340
+ rafId = 0;
341
+ }
342
+ }
343
+ };
284
344
  }
285
345
 
286
346
  // src/utils/colors.ts
@@ -313,7 +373,6 @@ var CanvasRenderer = class {
313
373
  render(ctx, viewport, shapes) {
314
374
  ctx.save();
315
375
  ctx.translate(viewport.x, viewport.y);
316
- ctx.rotate(viewport.rotation);
317
376
  ctx.scale(viewport.zoom, viewport.zoom);
318
377
  for (const shape of shapes) {
319
378
  if (shape.type === "draw") {
@@ -806,7 +865,7 @@ var PenDrawingState = class extends StateNode {
806
865
  const z = this._startInfo?.point?.z ?? 0.5;
807
866
  this._isPenDevice = penActive;
808
867
  this._hasPressure = penActive || z !== 0.5;
809
- const pressure = this._hasPressure ? toFixed(z) : 0.5;
868
+ const pressure = this._hasPressure ? toFixed(z * 1.25) : 0.5;
810
869
  this._phase = inputs.getShiftKey() ? "straight" : "free";
811
870
  this._extending = false;
812
871
  this._lastSample = { ...origin };
@@ -903,13 +962,12 @@ var PenDrawingState = class extends StateNode {
903
962
  const curPt = inputs.getCurrentPagePoint();
904
963
  if (!this._hasPressure) {
905
964
  const liveZ = curPt.z ?? 0.5;
906
- if (liveZ !== 0.5 || inputs.getIsPen()) {
965
+ if (liveZ > 0 && liveZ !== 0.5 || inputs.getIsPen()) {
907
966
  this._hasPressure = true;
908
- this.editor.updateShapes([{ id, type: "draw", props: { isPen: true } }]);
909
967
  }
910
968
  }
911
969
  const local = this.editor.getPointInShapeSpace(shape, curPt);
912
- const pressure = this._hasPressure ? toFixed(curPt.z ?? 0.5) : 0.5;
970
+ const pressure = this._hasPressure ? toFixed((curPt.z ?? 0.5) * 1.25) : 0.5;
913
971
  const pt = { x: toFixed(local.x), y: toFixed(local.y), z: pressure };
914
972
  switch (this._phase) {
915
973
  case "starting_straight": {
@@ -1055,7 +1113,7 @@ var PenDrawingState = class extends StateNode {
1055
1113
  const firstPt = {
1056
1114
  x: 0,
1057
1115
  y: 0,
1058
- z: this._hasPressure ? toFixed(curPage.z ?? 0.5) : 0.5
1116
+ z: this._hasPressure ? toFixed((curPage.z ?? 0.5) * 1.25) : 0.5
1059
1117
  };
1060
1118
  this._activePts = [firstPt];
1061
1119
  this.editor.createShape({
@@ -1087,7 +1145,7 @@ var PenDrawingState = class extends StateNode {
1087
1145
  endStroke() {
1088
1146
  if (!this._target) return;
1089
1147
  this.editor.updateShapes([
1090
- { id: this._target.id, type: "draw", props: { isComplete: true } }
1148
+ { id: this._target.id, type: "draw", props: { isComplete: true, isPen: this._hasPressure } }
1091
1149
  ]);
1092
1150
  this.ctx.transition("pen_idle");
1093
1151
  }
@@ -1517,16 +1575,29 @@ var HandIdleState = class extends StateNode {
1517
1575
  // src/tools/hand/states/HandDraggingState.ts
1518
1576
  var HandDraggingState = class extends StateNode {
1519
1577
  static id = "hand_dragging";
1578
+ panSession = null;
1579
+ onEnter(info) {
1580
+ const downInfo = info;
1581
+ const screenX = downInfo?.screenX ?? 0;
1582
+ const screenY = downInfo?.screenY ?? 0;
1583
+ this.panSession = beginCameraPan(this.editor.viewport, screenX, screenY);
1584
+ }
1520
1585
  onPointerMove(info) {
1521
- const move = info ?? {};
1522
- const dx = move.screenDeltaX ?? 0;
1523
- const dy = move.screenDeltaY ?? 0;
1524
- if (dx === 0 && dy === 0) return;
1525
- this.editor.panBy(dx, dy);
1586
+ if (!this.panSession) return;
1587
+ const screenX = info?.screenX ?? 0;
1588
+ const screenY = info?.screenY ?? 0;
1589
+ const target = moveCameraPan(this.panSession, screenX, screenY);
1590
+ this.editor.setViewport({ x: target.x, y: target.y });
1591
+ }
1592
+ getPanSession() {
1593
+ return this.panSession;
1526
1594
  }
1527
1595
  onPointerUp() {
1528
1596
  this.ctx.transition("hand_idle");
1529
1597
  }
1598
+ onExit() {
1599
+ this.panSession = null;
1600
+ }
1530
1601
  onCancel() {
1531
1602
  this.ctx.transition("hand_idle");
1532
1603
  }
@@ -1754,8 +1825,7 @@ var Editor = class {
1754
1825
  this.viewport = {
1755
1826
  x: partial.x ?? this.viewport.x,
1756
1827
  y: partial.y ?? this.viewport.y,
1757
- zoom: Math.max(0.1, Math.min(4, rawZoom)),
1758
- rotation: partial.rotation ?? this.viewport.rotation
1828
+ zoom: Math.max(0.1, Math.min(4, rawZoom))
1759
1829
  };
1760
1830
  this.emitChange();
1761
1831
  }
@@ -1769,10 +1839,6 @@ var Editor = class {
1769
1839
  this.viewport = zoomViewport(this.viewport, factor, screenX, screenY);
1770
1840
  this.emitChange();
1771
1841
  }
1772
- rotateAt(delta, screenX, screenY) {
1773
- this.viewport = rotateViewport(this.viewport, delta, screenX, screenY);
1774
- this.emitChange();
1775
- }
1776
1842
  deleteShapes(ids) {
1777
1843
  if (ids.length === 0) return;
1778
1844
  this.store.deleteShapes(ids);
@@ -1795,8 +1861,7 @@ var Editor = class {
1795
1861
  viewport: {
1796
1862
  x: this.viewport.x,
1797
1863
  y: this.viewport.y,
1798
- zoom: this.viewport.zoom,
1799
- rotation: this.viewport.rotation
1864
+ zoom: this.viewport.zoom
1800
1865
  },
1801
1866
  currentToolId: this.getCurrentToolId(),
1802
1867
  drawStyle: this.getCurrentDrawStyle(),
@@ -1804,10 +1869,7 @@ var Editor = class {
1804
1869
  };
1805
1870
  }
1806
1871
  loadSessionStateSnapshot(snapshot) {
1807
- this.setViewport({
1808
- ...snapshot.viewport,
1809
- rotation: snapshot.viewport.rotation ?? 0
1810
- });
1872
+ this.setViewport(snapshot.viewport);
1811
1873
  this.setCurrentDrawStyle({
1812
1874
  color: snapshot.drawStyle.color,
1813
1875
  dash: snapshot.drawStyle.dash,
@@ -2220,6 +2282,7 @@ exports.ToolManager = ToolManager;
2220
2282
  exports.applyMove = applyMove;
2221
2283
  exports.applyResize = applyResize;
2222
2284
  exports.applyRotation = applyRotation;
2285
+ exports.beginCameraPan = beginCameraPan;
2223
2286
  exports.boundsContainPoint = boundsContainPoint;
2224
2287
  exports.boundsIntersect = boundsIntersect;
2225
2288
  exports.boundsOf = boundsOf;
@@ -2240,6 +2303,7 @@ exports.getShapesInBounds = getShapesInBounds;
2240
2303
  exports.getTopShapeAtPoint = getTopShapeAtPoint;
2241
2304
  exports.isSelectTool = isSelectTool;
2242
2305
  exports.minDistanceToPolyline = minDistanceToPolyline;
2306
+ exports.moveCameraPan = moveCameraPan;
2243
2307
  exports.normalizeSelectionBounds = normalizeSelectionBounds;
2244
2308
  exports.padBounds = padBounds;
2245
2309
  exports.pageToScreen = pageToScreen;
@@ -2248,13 +2312,13 @@ exports.pointHitsShape = pointHitsShape;
2248
2312
  exports.recordsToDocumentSnapshot = recordsToDocumentSnapshot;
2249
2313
  exports.resolveThemeColor = resolveThemeColor;
2250
2314
  exports.rotatePoint = rotatePoint;
2251
- exports.rotateViewport = rotateViewport;
2252
2315
  exports.screenToPage = screenToPage;
2253
2316
  exports.segmentHitsShape = segmentHitsShape;
2254
2317
  exports.segmentTouchesPolyline = segmentTouchesPolyline;
2255
2318
  exports.setViewport = setViewport;
2256
2319
  exports.shapePagePoints = shapePagePoints;
2257
2320
  exports.sqDistance = sqDistance;
2321
+ exports.startCameraSlide = startCameraSlide;
2258
2322
  exports.zoomViewport = zoomViewport;
2259
2323
  //# sourceMappingURL=index.cjs.map
2260
2324
  //# sourceMappingURL=index.cjs.map