gpucanvas 0.2.0 → 0.3.0

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/README.md CHANGED
@@ -18,7 +18,7 @@ The GPU path needs HTML-in-Canvas, which is behind a flag:
18
18
 
19
19
  Without the flag the demo runs the DOM fallback with 1,000 screens and says so.
20
20
 
21
- Status: early (0.2). The API can still change.
21
+ Status: early (0.3). The API can still change.
22
22
 
23
23
  ```sh
24
24
  npm install gpucanvas
@@ -41,17 +41,128 @@ HTML-in-Canvas are available (Chrome 157+), nodes are snapshotted into a GPU atl
41
41
  one instanced draw call. Everywhere else it falls back to a CSS-transformed container with the
42
42
  same API.
43
43
 
44
- ## Selection
44
+ ## Editing
45
45
 
46
- Selected nodes get an outline drawn by the GPU, 2 CSS pixels wide at any zoom.
46
+ `bindEditor` turns the canvas into an editor: select, drill in, move and resize. The GPU draws
47
+ the outlines, 2 CSS pixels wide at any zoom, and they follow their element's layout. Names,
48
+ sizes, handles and snapping guides sit in a thin overlay above it.
47
49
 
48
50
  ```ts
49
- const hits = canvas.getNodesIn({ x, y, width, height }); // world units, such as a selection box
50
- canvas.setSelection(hits); // replaces the selection; [] clears it
51
+ import { bindEditor } from 'gpucanvas';
52
+
53
+ const editor = bindEditor(canvas, {
54
+ pick: 'element', // 'node' (default), 'element', or a function
55
+ trigger: 'click', // an edit mode; the default 'shift-click' leaves clicks to your components
56
+ onChange: (layers) => console.log(layers),
57
+ onTransform: ({ phase, layers, to }) => {
58
+ // The editor never moves anything itself. Apply `to` with updateNode, state or styles.
59
+ },
60
+ });
51
61
  ```
52
62
 
53
- The color is the `selectionColor` option. Shift-drags on the background don't pan, so an app
54
- can use them for a selection box, as the demo does.
63
+ A click selects the outermost layer under the pointer, a whole node. Each double-click goes one
64
+ layer deeper, and later clicks stay at that depth. Wrappers the size of their parent are skipped,
65
+ so every double-click visibly changes something.
66
+
67
+ | Input | Does |
68
+ | ------------------ | ---------------------------------------------------------------------------- |
69
+ | Double-click | One layer deeper |
70
+ | Enter, Shift+Enter | First child, parent |
71
+ | Tab, Shift+Tab | Next and previous sibling |
72
+ | Escape | Cancels a drag, then goes up a layer, then clears |
73
+ | Drag | Moves, snapping to the edges and centers of neighbors (Shift locks the axis) |
74
+ | Handles | Resize (Shift on a corner keeps the ratio) |
75
+ | Drag on open space | A box that selects the layers it touches, inside the layer you are in |
76
+
77
+ With the default `trigger: 'shift-click'`, the same works with Shift held, and clicks still reach
78
+ your components. A layer is an array of elements, so one layer can be a component that renders
79
+ several. `pick` decides the layers:
80
+
81
+ - `'node'`: whole nodes only.
82
+ - `'element'`: every element from the node down to the one under the pointer.
83
+ - a function of that element, returning the layers that hold it, outermost first.
84
+
85
+ In React, `useEditor` gives the selection as state, and `pickComponents` makes the layers React
86
+ components:
87
+
88
+ ```tsx
89
+ import { labelComponent, pickComponents, useEditor } from 'gpucanvas/react';
90
+
91
+ // Inside <GpuCanvas>:
92
+ const { selected, clear } = useEditor({ pick: pickComponents, label: labelComponent });
93
+ // Click a screen, double-click: SignIn, SignInCard, Button.
94
+ ```
95
+
96
+ `pickComponents` and `componentOf` read React's internal fibers, as click-to-component tools do.
97
+ Production builds usually minify component names, so mark components with your own attributes
98
+ when you need names there.
99
+
100
+ The building blocks, for your own interactions:
101
+
102
+ ```ts
103
+ canvas.getElementAt(point); // deepest element at a world point
104
+ canvas.getElementRect(el); // any element's rect in world units
105
+ canvas.getNodeOf(el); // the node that holds it
106
+ canvas.getNodesIn(rect); // nodes overlapping a world rect
107
+ canvas.setSelection(elements); // outline these; [] clears
108
+ canvas.setHover(el); // a lighter outline; null clears
109
+ canvas.screenToWorld(point); // surface pixels to world units, and worldToScreen back
110
+ ```
111
+
112
+ Colors are the `selectionColor` and `hoverColor` options. The demo picks React components in the edit mode;
113
+ `?pick=element` or `?pick=node` picks elements or whole screens, and `?edit=0` leaves clicks to
114
+ the components.
115
+
116
+ ## Shader effects
117
+
118
+ Components stay live DOM, and the GPU draws them through a shader. One call per node:
119
+
120
+ ```ts
121
+ import { effects } from 'gpucanvas';
122
+
123
+ canvas.setEffect(node, effects.ripple); // the effect's default parameters
124
+ canvas.setEffect(node, effects.glow, [1, 0.4, 0.2, 32]); // orange, 32 px reach
125
+ canvas.setEffect(node, null); // back to plain
126
+ ```
127
+
128
+ Built in:
129
+
130
+ - Following the pointer: `glass` (a magnifying lens), `frost` (ice, clear around the pointer),
131
+ `dither` (a lens of big dithered pixels).
132
+ - On each press: `shockwave`, a ring that bends the node as it spreads.
133
+ - Running on their own: `glitch`, `vhs`, `glow`, `hue`, `ripple`.
134
+ - Still: `blur`, `pixelate`, `grayscale`, and `dissolve` for a node leaving or arriving (its
135
+ parameter is the progress, 0 to 1).
136
+
137
+ Animated effects draw every frame while their node is in view; still ones cost nothing once
138
+ drawn.
139
+
140
+ Your own is a WGSL function over the node's pixels:
141
+
142
+ ```ts
143
+ import { defineEffect } from 'gpucanvas';
144
+
145
+ const scanlines = defineEffect({
146
+ name: 'scanlines',
147
+ animated: true,
148
+ params: [3], // line spacing in CSS pixels
149
+ wgsl: /* wgsl */ `
150
+ fn effect(input: EffectInput) -> vec4f {
151
+ let color = source(input.uv);
152
+ let y = input.uv.y * input.size.y + input.time * 20.0;
153
+ return color * (0.85 + 0.15 * step(0.5, fract(y / input.params.x)));
154
+ }`,
155
+ });
156
+ ```
157
+
158
+ `EffectInput` has `uv` (0 to 1 across the node), `size` in CSS pixels, `time` in seconds,
159
+ `zoom`, the four `params`, the `pointer` and the last `press` in the node's uv, and
160
+ `sincePress` in seconds. `source(uv)` reads the node's pixels, premultiplied, and is
161
+ transparent outside it; `hash` and `noise` give random values. An effect that draws past its node, like a glow, declares how far with
162
+ `pad`. Effects read the node's whole snapshot, so very close in they are softer than plain nodes.
163
+
164
+ The DOM backend shows effects that have a `css` look as a CSS `filter` (animated ones as filter
165
+ animations) and the rest as plain nodes. The demo's toolbar puts one effect on every component.
55
166
 
56
167
  ## Run the demo
57
168