@einfach/canvas 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +67 -2
- package/package.json +34 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) dol
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,68 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @einfach/canvas
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Canvas cell 的基础包:cell 生命周期、atom 直接订阅、按帧合并重绘、区域缓存。
|
|
4
|
+
|
|
5
|
+
借鉴 Solid 的 owner / cleanup 生命周期。状态与派生依赖继续由 `@einfach/core` 管理;`useAtomValue` 在 cell 初始化时直接调用 `store.sub`,通知时标记这个 cell 待重绘,销毁时调用 unsubscribe。没有 DOM 绑定、JSX、props 系统或另一套响应式计算图。
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { atom, createStore } from '@einfach/core'
|
|
9
|
+
import { createCanvas, useAtomValue, useCache } from '@einfach/canvas'
|
|
10
|
+
|
|
11
|
+
const store = createStore()
|
|
12
|
+
const surface = createCanvas(canvasElement, { store, width: 800, height: 600 })
|
|
13
|
+
const getCellAtom = surface.run(() => useCache((id: number) => atom(id)))
|
|
14
|
+
|
|
15
|
+
const cell = surface.createCell({ x: 0, y: 0, width: 100, height: 32 }, () => {
|
|
16
|
+
const value = useAtomValue(getCellAtom(0)) // 初始化只执行一次,立即订阅
|
|
17
|
+
return (ctx, rect) => { // requestAnimationFrame 中绘制
|
|
18
|
+
ctx.fillStyle = '#fff'
|
|
19
|
+
ctx.fillRect(rect.x, rect.y, rect.width, rect.height)
|
|
20
|
+
ctx.fillStyle = '#27364c'
|
|
21
|
+
ctx.fillText(String(value()), rect.x + 8, rect.y + 22)
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
store.setter(getCellAtom(0), 42) // 标记 cell 待重绘,自动安排下一帧
|
|
26
|
+
cell.dispose() // 取消订阅,下一帧清除原绘制区域
|
|
27
|
+
surface.dispose() // 销毁所有 cell、清空画布、取消帧、使缓存失效
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`createCanvas` 建立独立的区域 root,由调用方负责 `dispose()`。`createCell(bounds, setup)` 创建这个区域的子生命周期,setup 返回 painter。painter 只负责绘图,通过初始化时得到的 getter 读取最新 atom 值;不要在 painter 中创建订阅。
|
|
31
|
+
|
|
32
|
+
同一帧的多次通知会合并。绘制时先清除脏区域,再按创建顺序重绘与脏区域相交的 cell;每个 painter 被裁剪到自身 bounds。这样重叠 cell 在局部更新或删除后仍保持正确的覆盖关系。适合矩形 cell;布局与虚拟滚动由上层负责。
|
|
33
|
+
|
|
34
|
+
在 cell setup 中使用 `onPointerDown`、`onPointerMove`、`onPointerUp`、`onPointerCancel`、`onPointerEnter`、`onPointerLeave` 注册事件。每张 Canvas 统一监听原生事件,按绘制顺序反向命中矩形 cell。事件包含原生 `nativeEvent`、画布逻辑坐标 `x/y` 和 cell 内坐标 `localX/localY`;不建立冒泡树。
|
|
35
|
+
|
|
36
|
+
按下时调用 `event.capturePointer()`,后续 move/up 会继续发给该 cell,即使指针已经移出画布。up/cancel 自动释放,也可以调用 `event.releasePointer()`。hover 独立命中实际指针所在 cell;滚动会重新计算 hover。cell 销毁时先发送取消和离开事件,再移除处理函数;区域销毁移除所有原生监听。选区、Shift 扩展和 Escape 等业务行为由 demo 实现,未包含拖拽自动滚动。
|
|
37
|
+
|
|
38
|
+
| API | 行为 |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| `surface.createCell(bounds, setup)` | 创建 cell,返回 `{ invalidate, dispose }` |
|
|
41
|
+
| `surface.run(fn)` | 进入区域 owner,适合创建共享缓存 |
|
|
42
|
+
| `surface.invalidate()` | 标记整个画布待重绘 |
|
|
43
|
+
| `surface.flush()` | 立即执行待绘制内容,便于显式同步或测试 |
|
|
44
|
+
| `surface.resize(width, height, pixelRatio?)` | 使用逻辑像素尺寸,按像素比例调整画布并重绘 |
|
|
45
|
+
| `surface.dispose()` | 同步解绑资源并清空画布,可重复调用 |
|
|
46
|
+
| `useAtomValue(atom, { store? })` | 在 cell setup 中直接订阅,返回读取当前值的 getter |
|
|
47
|
+
| `useSetAtom(atom, { store? })` | 获得 Core setter |
|
|
48
|
+
| `onCleanup(fn)` | 注册当前 cell / owner 的清理逻辑 |
|
|
49
|
+
| `useCache(factory, equality?)` | 同区域共享 factory 与参数对应的结果 |
|
|
50
|
+
|
|
51
|
+
`createRoot`、`createScope`、`getOwner`、`runWithOwner` 与 context 原语供上层组织生命周期。scope 随父级销毁,root 需要独立销毁。清理失败仍会继续释放其余资源,再抛出错误。
|
|
52
|
+
|
|
53
|
+
缓存借鉴现有区域缓存:WeakMap 弱引用 factory,参数和结果在区域存活时保留。子 cell 销毁不会删掉区域缓存;区域销毁主动清空整个缓存,已有缓存包装函数随后调用会报错。默认逐项 `Object.is` 比较参数,也支持完整参数数组比较器。factory 应创建 atom 定义或普通数据。
|
|
54
|
+
|
|
55
|
+
Canvas 创建时捕获 `devicePixelRatio`,可通过 `resize` 更新。painter 坐标始终是逻辑像素。`onFrame({ cells, elapsed })` 报告实际调用的 painter 数与同步绘制耗时,不包含浏览器合成。
|
|
56
|
+
|
|
57
|
+
本包只清理自己的订阅和缓存,不会对传入的共享 store 自动调用 `clear()`;Core 的派生 atom 依赖图仍由 Core 管理。区域若独占 store,可在 `surface.dispose()` 后清理该 store。
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
pnpm --filter @einfach/canvas test
|
|
61
|
+
pnpm --filter @einfach/canvas typecheck
|
|
62
|
+
pnpm --filter @einfach/canvas build
|
|
63
|
+
pnpm --filter @einfach/canvas-demo dev
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
规则网格可以设置 `pointerEvents: false` 关闭通用 cell 事件分发,由上层使用行列索引实现命中;这时 `onPointer*` hooks 不会收到事件。`canvas-cells` 使用此模式,避免每次指针移动扫描 cell。
|
|
67
|
+
|
|
68
|
+
`setViewport(rect)` 指定画布显示的世界坐标区域,实际像素缓冲只按其宽高分配。`CanvasCell.setBounds(rect)` 更新 cell 位置并清理旧区域,不重建 owner 或订阅。对按世界坐标绘制并正确调用失效通知的 painter,可设置 `reusePixels: true`,滚动时复制重叠像素并补画新露出的区域;不满足精确像素平移条件时自动完整重画。
|
package/package.json
CHANGED
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@einfach/canvas",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Canvas cells with owned atom subscriptions and region caches",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"@einfach/core": "^0.4.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@einfach/core": "^0.4.0",
|
|
26
|
+
"@types/node": "^22.19.0",
|
|
27
|
+
"typescript": "^5.7.2",
|
|
28
|
+
"vitest": "5.0.5",
|
|
29
|
+
"vite": "8.3.4"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.build.json",
|
|
33
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
34
|
+
"test": "vitest run",
|
|
35
|
+
"test:watch": "vitest"
|
|
36
|
+
}
|
|
6
37
|
}
|