star-canvas 0.1.8 → 0.1.9

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/PROMPT.md CHANGED
@@ -124,6 +124,7 @@ A safe manager for your HTML overlay, stacked on top of the canvas.
124
124
  - `ui.render(html: string)`: **Use this** to set your UI. It's safe and won't destroy the canvas.
125
125
  - Automatically skips updates if HTML is unchanged (safe to call in loop for static content)
126
126
  - For best performance with dynamic content (score), only call when values actually change
127
+ - **Positioning:** The UI overlay covers the full viewport, not just the letterboxed game area. Use percentage-based positioning (e.g., `top: 50%; left: 50%; transform: translate(-50%, -50%)`) to stay centered within the visible area. Fixed offsets like `bottom: 18px` may land in the letterbox bars on some screen sizes.
127
128
  - `ui.el(selector)`: Scoped `querySelector` for the UI root.
128
129
  - `ui.all(selector)`: Scoped `querySelectorAll` for the UI root.
129
130
 
@@ -180,6 +181,15 @@ g.loop((dt) => {
180
181
  });
181
182
  ```
182
183
 
184
+ **Hover effects for canvas buttons:**
185
+ ```ts
186
+ g.loop((dt) => {
187
+ const hoverLb = state === 'gameover' && inRect(g.pointer, lbBtn);
188
+ drawButton('VIEW LEADERBOARD', lbBtn, hoverLb ? '#9061f9' : '#7c3aed');
189
+ g.canvas.style.cursor = hoverLb ? 'pointer' : 'default';
190
+ });
191
+ ```
192
+
183
193
  Taps on interactive UI elements (from `on()` or native `<button>`) are automatically suppressed from `g.tap`.
184
194
 
185
195
  ### Cursor Management
@@ -202,39 +212,34 @@ if (state === 'menu' || state === 'gameover') canvas.style.cursor = 'auto'; //
202
212
 
203
213
  **Decision:** Does player click on game objects? → `'pointer'` | Aim precisely? → `'crosshair'` | WASD/touch only? → `'none'` during play, `'auto'` for menus
204
214
 
205
- ### `toStagePoint(event)`
206
-
207
- Converts `MouseEvent` or `PointerEvent` client coordinates to the stage's logical coordinates.
215
+ ### Drag and Drop (Polling Pattern)
208
216
 
209
- - **USE THIS** for all canvas pointer input.
210
-
211
- ### `createDrag()`
212
-
213
- Creates a drag state helper that handles coordinate conversion and offset tracking automatically.
217
+ Use `g.tap`, `g.pointer`, and `g.released` for drag-and-drop coordinates are automatic.
214
218
 
215
219
  ```ts
216
- const drag = createDrag();
220
+ let dragging = null;
221
+ let offsetX = 0, offsetY = 0;
217
222
 
218
- canvas.addEventListener('pointerdown', (e) => {
219
- canvas.setPointerCapture(e.pointerId); // IMPORTANT: Capture for reliable drags
220
- const { x, y } = drag.point(e); // Convert coordinates
221
- const hit = pieces.find(p => /* hit test */);
222
- if (hit) drag.grab(e, hit); // Start drag with offset
223
- });
224
-
225
- canvas.addEventListener('pointermove', (e) => drag.move(e)); // Updates position
226
- canvas.addEventListener('pointerup', () => {
227
- const dropped = drag.release(); // Returns dropped object (or null)
223
+ g.loop((dt) => {
224
+ if (g.tap) {
225
+ const hit = pieces.find(p => inRect(g.tap, p));
226
+ if (hit) {
227
+ dragging = hit;
228
+ offsetX = hit.x - g.tap.x; // Offset so piece doesn't jump to cursor
229
+ offsetY = hit.y - g.tap.y;
230
+ }
231
+ }
232
+ if (dragging && g.pointer.down) {
233
+ dragging.x = g.pointer.x + offsetX;
234
+ dragging.y = g.pointer.y + offsetY;
235
+ }
236
+ if (g.released && dragging) {
237
+ handleDrop(dragging);
238
+ dragging = null;
239
+ }
228
240
  });
229
241
  ```
230
242
 
231
- **API:**
232
- - `point(e)` - Pure coordinate conversion, no side effects
233
- - `grab(e, obj)` - Start dragging an object, computing offset from cursor
234
- - `move(e)` - Update dragged object's position
235
- - `release()` - End drag, returns dropped object or null
236
- - `dragging` - The currently dragged object (or null)
237
-
238
243
  ### `GameOptions` (optional)
239
244
 
240
245
  Pass an options object as the second argument to `game()`:
@@ -508,15 +513,15 @@ game(({ ctx, scoped, loop }) => {
508
513
 
509
514
  **Why use `scoped()`:** Prevents transform stack corruption from early returns, exceptions, or forgetting `ctx.restore()`. The context is always restored, even if the function exits early.
510
515
 
511
- ### Recipe 7: Drag and Drop with createDrag() (RECOMMENDED)
516
+ ### Recipe 7: Drag and Drop
512
517
 
513
- Use the `createDrag()` helper - it handles coordinate conversion and offset tracking automatically.
518
+ Use `g.tap`, `g.pointer`, and `g.released` for drag-and-drop. Coordinates are canvas-space automatically.
514
519
 
515
520
  ```ts
516
521
  import { game } from 'star-canvas';
517
522
 
518
- game(({ ctx, width, height, loop, canvas, createDrag }) => {
519
- // Size relative to height for consistency
523
+ game((g) => {
524
+ const { ctx, width, height } = g;
520
525
  const pieceSize = height * 0.15;
521
526
 
522
527
  const pieces = [
@@ -525,77 +530,8 @@ game(({ ctx, width, height, loop, canvas, createDrag }) => {
525
530
  { x: width * 0.6, y: height * 0.3, color: '#3b82f6' },
526
531
  ];
527
532
 
528
- // Create drag helper - handles coordinate conversion automatically
529
- const drag = createDrag();
530
-
531
- function hitTest(x, y) {
532
- for (let i = pieces.length - 1; i >= 0; i--) {
533
- const p = pieces[i];
534
- if (x >= p.x && x < p.x + pieceSize && y >= p.y && y < p.y + pieceSize) {
535
- return p;
536
- }
537
- }
538
- return null;
539
- }
540
-
541
- canvas.addEventListener('pointerdown', (e) => {
542
- canvas.setPointerCapture(e.pointerId); // IMPORTANT: Ensures drag works outside canvas
543
- const { x, y } = drag.point(e); // Convert coordinates
544
- const hit = hitTest(x, y);
545
- if (hit) {
546
- drag.grab(e, hit); // Start drag with offset from cursor
547
- canvas.style.cursor = 'grabbing';
548
- }
549
- });
550
-
551
- canvas.addEventListener('pointermove', (e) => {
552
- drag.move(e); // Updates grabbed object position
553
- if (!drag.dragging) {
554
- const { x, y } = drag.point(e);
555
- canvas.style.cursor = hitTest(x, y) ? 'grab' : 'default';
556
- }
557
- });
558
-
559
- canvas.addEventListener('pointerup', () => {
560
- const dropped = drag.release(); // Returns dropped object (or null)
561
- if (dropped) {
562
- console.log('Dropped:', dropped);
563
- }
564
- canvas.style.cursor = 'default';
565
- });
566
-
567
- loop(() => {
568
- ctx.fillStyle = '#1f2937';
569
- ctx.fillRect(0, 0, width, height);
570
-
571
- for (const p of pieces) {
572
- ctx.fillStyle = drag.dragging === p ? '#f59e0b' : p.color;
573
- ctx.fillRect(p.x, p.y, pieceSize, pieceSize);
574
- }
575
- });
576
- });
577
- ```
578
-
579
- **CRITICAL: Always use `setPointerCapture()`** - This ensures drags work even when the pointer moves outside the canvas. Without it, fast drags can leave objects stuck mid-drag.
580
-
581
- ### Recipe 8: Drag and Drop (Manual Pattern)
582
-
583
- If you need more control, here's the manual approach with `toStagePoint()`.
584
-
585
- ```ts
586
- import { game } from 'star-canvas';
587
-
588
- game(({ ctx, width, height, loop, canvas, toStagePoint }) => {
589
- const pieceSize = height * 0.15;
590
- const pieces = [
591
- { x: width * 0.2, y: height * 0.3, color: '#ef4444' },
592
- { x: width * 0.4, y: height * 0.4, color: '#10b981' },
593
- ];
594
-
595
- // Manual drag state
596
533
  let dragging = null;
597
- let dragOffsetX = 0;
598
- let dragOffsetY = 0;
534
+ let offsetX = 0, offsetY = 0;
599
535
 
600
536
  function hitTest(px, py) {
601
537
  for (let i = pieces.length - 1; i >= 0; i--) {
@@ -607,34 +543,28 @@ game(({ ctx, width, height, loop, canvas, toStagePoint }) => {
607
543
  return null;
608
544
  }
609
545
 
610
- canvas.addEventListener('pointerdown', (e) => {
611
- canvas.setPointerCapture(e.pointerId); // Ensures drag works outside canvas
612
- const { x, y } = toStagePoint(e); // CRITICAL: Convert coordinates!
613
- const hit = hitTest(x, y);
614
- if (hit) {
615
- dragging = hit;
616
- dragOffsetX = x - hit.x; // Store offset
617
- dragOffsetY = y - hit.y;
618
- canvas.style.cursor = 'grabbing';
546
+ g.loop(() => {
547
+ // Grab
548
+ if (g.tap) {
549
+ const hit = hitTest(g.tap.x, g.tap.y);
550
+ if (hit) {
551
+ dragging = hit;
552
+ offsetX = hit.x - g.tap.x; // Offset so piece doesn't jump to cursor
553
+ offsetY = hit.y - g.tap.y;
554
+ }
619
555
  }
620
- });
621
-
622
- canvas.addEventListener('pointermove', (e) => {
623
- const { x, y } = toStagePoint(e); // CRITICAL: Convert here too!
624
- if (dragging) {
625
- dragging.x = x - dragOffsetX;
626
- dragging.y = y - dragOffsetY;
627
- } else {
628
- canvas.style.cursor = hitTest(x, y) ? 'grab' : 'default';
556
+ // Move
557
+ if (dragging && g.pointer.down) {
558
+ dragging.x = g.pointer.x + offsetX;
559
+ dragging.y = g.pointer.y + offsetY;
560
+ }
561
+ // Drop
562
+ if (g.released && dragging) {
563
+ console.log('Dropped:', dragging);
564
+ dragging = null;
629
565
  }
630
- });
631
-
632
- canvas.addEventListener('pointerup', () => {
633
- dragging = null;
634
- canvas.style.cursor = 'default';
635
- });
636
566
 
637
- loop(() => {
567
+ // Draw
638
568
  ctx.fillStyle = '#1f2937';
639
569
  ctx.fillRect(0, 0, width, height);
640
570
 
@@ -646,16 +576,6 @@ game(({ ctx, width, height, loop, canvas, toStagePoint }) => {
646
576
  });
647
577
  ```
648
578
 
649
- **Common Drag-Drop Mistakes:**
650
-
651
- 1. ❌ Forgetting `toStagePoint()` in pointermove → `createDrag()` fixes this
652
- 2. ❌ No drag offset (piece "jumps" to cursor) → `createDrag()` fixes this
653
- 3. ❌ Using `e.clientX/clientY` directly → `createDrag()` fixes this
654
- 4. ❌ Not clearing state on pointerup → `createDrag()` fixes this
655
- 5. ❌ Missing `setPointerCapture()` (drags break outside canvas) → **You must add this!**
656
-
657
- **Recommendation:** Use `createDrag()` + `setPointerCapture()` for bulletproof drag-and-drop.
658
-
659
579
  ### Recipe 9: Image Backgrounds
660
580
 
661
581
  Two patterns for backgrounds: **full-canvas** (unique scenes) or **tileable patterns** (repeating textures).
package/dist/index.d.cts CHANGED
@@ -72,7 +72,7 @@ interface GameContext {
72
72
  x: number;
73
73
  y: number;
74
74
  };
75
- /** Creates a drag state helper for pointer-based dragging. Handles coordinate conversion and offset. */
75
+ /** @deprecated Use g.tap/g.pointer/g.released polling instead. */
76
76
  createDrag: <T extends {
77
77
  x: number;
78
78
  y: number;
@@ -192,7 +192,7 @@ interface DragState<T extends {
192
192
  /** The currently grabbed object (or null) */
193
193
  readonly dragging: T | null;
194
194
  }
195
- /** Creates a drag state helper that handles coordinate conversion and offset tracking */
195
+ /** @deprecated Use g.tap/g.pointer/g.released polling for drag-and-drop instead. */
196
196
  declare function createDragState<T extends {
197
197
  x: number;
198
198
  y: number;
package/dist/index.d.ts CHANGED
@@ -72,7 +72,7 @@ interface GameContext {
72
72
  x: number;
73
73
  y: number;
74
74
  };
75
- /** Creates a drag state helper for pointer-based dragging. Handles coordinate conversion and offset. */
75
+ /** @deprecated Use g.tap/g.pointer/g.released polling instead. */
76
76
  createDrag: <T extends {
77
77
  x: number;
78
78
  y: number;
@@ -192,7 +192,7 @@ interface DragState<T extends {
192
192
  /** The currently grabbed object (or null) */
193
193
  readonly dragging: T | null;
194
194
  }
195
- /** Creates a drag state helper that handles coordinate conversion and offset tracking */
195
+ /** @deprecated Use g.tap/g.pointer/g.released polling for drag-and-drop instead. */
196
196
  declare function createDragState<T extends {
197
197
  x: number;
198
198
  y: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "star-canvas",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "private": false,
5
5
  "description": "Canvas game utilities for reliable game initialization - part of Star SDK.",
6
6
  "type": "module",