ice-web-components 1.0.0 → 1.2.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 +93 -23
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/types/components/ICETileMap.d.ts +12 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/model/ICEChip8Model.d.ts +86 -0
- package/dist/types/model/ICEHistoryModel.d.ts +45 -0
- package/dist/types/model/ICEPixelModel.d.ts +89 -0
- package/package.json +2 -1
|
@@ -90,6 +90,9 @@ export declare class ICETileMap extends ICEWidget {
|
|
|
90
90
|
private highlightColor;
|
|
91
91
|
private labelFontSize;
|
|
92
92
|
private labelColor;
|
|
93
|
+
/** 构造期是否显式给过 width/height(`props` 里已被默认值填过,只能在这里记一笔) */
|
|
94
|
+
private explicitWidth;
|
|
95
|
+
private explicitHeight;
|
|
93
96
|
/** 自绘流程执行次数(浏览器里由 render() 驱动;单测可直接调 paintBoard()) */
|
|
94
97
|
private paintCount;
|
|
95
98
|
constructor(props: ICETileMapOptions);
|
|
@@ -97,6 +100,15 @@ export declare class ICETileMap extends ICEWidget {
|
|
|
97
100
|
getCols(): number;
|
|
98
101
|
getCellSize(): number;
|
|
99
102
|
getGap(): number;
|
|
103
|
+
/**
|
|
104
|
+
* 换网格尺寸(行列 / 格子大小一起换),并清空格子数据与高亮。
|
|
105
|
+
*
|
|
106
|
+
* 为什么必须有这个方法:`rows/cols/cellSize` 在构造期就被缓存进实例字段,
|
|
107
|
+
* 只 `setState({ rows, cols })` 的话**内部字段还是旧的** —— 下一次 `setTiles` 会按旧尺寸
|
|
108
|
+
* 抛错(像素编辑器把 32×32 切成 16×16 时就是这么炸的)。
|
|
109
|
+
* 显式给过 `width/height` 的(想自己留白)保持不动,没给过的跟着格子尺寸重算。
|
|
110
|
+
*/
|
|
111
|
+
setSize(rows: number, cols: number, cellSize?: number): this;
|
|
100
112
|
/** 格子在组件内的矩形(gap 均分在两边)。 */
|
|
101
113
|
getCellRect(row: number, col: number): {
|
|
102
114
|
left: number;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -108,4 +108,7 @@ export * from './model/ICEMinesweeperModel';
|
|
|
108
108
|
export * from './model/ICETetrisModel';
|
|
109
109
|
export * from './model/ICESnakeModel';
|
|
110
110
|
export * from './model/ICEHighScoreModel';
|
|
111
|
+
export * from './model/ICEChip8Model';
|
|
111
112
|
export * from './model/ICE2048Model';
|
|
113
|
+
export * from './model/ICEHistoryModel';
|
|
114
|
+
export * from './model/ICEPixelModel';
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CHIP-8 虚拟机(纯逻辑,不碰 canvas)。
|
|
3
|
+
*
|
|
4
|
+
* 掌机的第四块卡带用它:前三个模型是"某款游戏的规则",这是一个**真的微处理器模拟器**——
|
|
5
|
+
* 4KB 内存、16 个 8 位寄存器、16 位地址寄存器 I、64×32 单色显存、两个 60Hz 定时器、16 键键盘。
|
|
6
|
+
* 画面交给 `ICETileMap`(2048 格仍然只有 1 个节点)画,音效走掌机那套 WebAudio。
|
|
7
|
+
*
|
|
8
|
+
* 支持指令(跑 demo / 小 ROM 足够):00E0 / 00EE / 1NNN / 2NNN / 3XNN / 4XNN / 5XY0 / 9XY0 /
|
|
9
|
+
* 6XNN / 7XNN / 8XY0-8XYE / 9XY0 / ANNN / BNNN / CXNN / DXYN / EX9E / EXA1 / FX07 / FX0A /
|
|
10
|
+
* FX15 / FX18 / FX1E / FX29 / FX33 / FX55 / FX65。
|
|
11
|
+
*/
|
|
12
|
+
export declare const ICE_CHIP8_KEYS: readonly ["1", "2", "3", "C", "4", "5", "6", "D", "7", "8", "9", "E", "A", "0", "B", "F"];
|
|
13
|
+
/**
|
|
14
|
+
* 自带的 demo ROM(自写,不依赖任何外部 ROM,避开版权)。
|
|
15
|
+
*
|
|
16
|
+
* 它做的事:清屏 → 在 (V0,V1) 画一个 8×8 笑脸 → 按 (V2,V3) 走一步 → 撞到边就把速度取反 → 跳回清屏重画。
|
|
17
|
+
* 也就是**在屏幕里来回弹**的笑脸:既不会裂到对边,也把条件跳过(4XNN)、补码减法(7XNN / 6XNN 的 0xFE)
|
|
18
|
+
* 和自建循环这几条最常用的指令演了一遍。
|
|
19
|
+
* 地址:程序 0x200 起(20 条指令 = 40 字节),精灵数据在 0x228(8 字节)。
|
|
20
|
+
*/
|
|
21
|
+
export declare const ICE_CHIP8_DEMO_ROM: number[];
|
|
22
|
+
export declare const ICE_CHIP8_WIDTH = 64;
|
|
23
|
+
export declare const ICE_CHIP8_HEIGHT = 32;
|
|
24
|
+
/** 状态变化通知(掌机外壳拿它驱动重绘,和另外三个游戏模型是同一套契约)。 */
|
|
25
|
+
export declare type ICEChip8Listener = (model: ICEChip8Model) => void;
|
|
26
|
+
export declare class ICEChip8Model {
|
|
27
|
+
private memory;
|
|
28
|
+
private v;
|
|
29
|
+
private i;
|
|
30
|
+
private pc;
|
|
31
|
+
private sp;
|
|
32
|
+
private stack;
|
|
33
|
+
private display;
|
|
34
|
+
private keys;
|
|
35
|
+
private delay;
|
|
36
|
+
private sound;
|
|
37
|
+
private cycles;
|
|
38
|
+
private random;
|
|
39
|
+
private paused;
|
|
40
|
+
/** 本条指令改了显存 → 执行完统一通知一次(别在 600 条/秒的频率上惊动渲染) */
|
|
41
|
+
private displayDirty;
|
|
42
|
+
private listeners;
|
|
43
|
+
/** FX0A 正在等按键时记住键位寄存器 */
|
|
44
|
+
private waitingForKey;
|
|
45
|
+
constructor(options?: {
|
|
46
|
+
random?: () => number;
|
|
47
|
+
});
|
|
48
|
+
getPC(): number;
|
|
49
|
+
getI(): number;
|
|
50
|
+
getV(index: number): number;
|
|
51
|
+
getDisplay(): Uint8Array;
|
|
52
|
+
getPixel(row: number, col: number): number;
|
|
53
|
+
getDelayTimer(): number;
|
|
54
|
+
getSoundTimer(): number;
|
|
55
|
+
getCycles(): number;
|
|
56
|
+
isWaitingForKey(): boolean;
|
|
57
|
+
memoryRead(address: number): number;
|
|
58
|
+
isKeyDown(index: number): boolean;
|
|
59
|
+
/** 暂停中(调试/切走标签页):`step()` 与 `tickTimers()` 都不推进。 */
|
|
60
|
+
isPaused(): boolean;
|
|
61
|
+
/** VM 没有"输赢",但掌机外壳按统一契约询问,这里老实回答"没结束"。 */
|
|
62
|
+
isGameOver(): boolean;
|
|
63
|
+
pause(): void;
|
|
64
|
+
resume(): void;
|
|
65
|
+
/**
|
|
66
|
+
* 订阅状态变化:显存画了新东西、按键变了、定时器走了、机器复位了都会通知。
|
|
67
|
+
* 返回取消订阅的函数(和其它模型一致)。
|
|
68
|
+
*/
|
|
69
|
+
addChangeListener(listener: ICEChip8Listener): () => void;
|
|
70
|
+
loadProgram(bytes: ArrayLike<number>, address?: number): void;
|
|
71
|
+
memoryWrite(address: number, value: number): void;
|
|
72
|
+
setKey(index: number, down: boolean): void;
|
|
73
|
+
/** 清掉所有按键(切卡带/失焦时用)。 */
|
|
74
|
+
clearKeys(): void;
|
|
75
|
+
setVForTest(index: number, value: number): void;
|
|
76
|
+
reset(): void;
|
|
77
|
+
/** 60Hz:递减延时/声音定时器。 */
|
|
78
|
+
tickTimers(): void;
|
|
79
|
+
/** 执行一条指令(FX0A 等按键时 PC 不动,直接返回)。 */
|
|
80
|
+
step(): void;
|
|
81
|
+
private notify;
|
|
82
|
+
private __alu;
|
|
83
|
+
/** DXYN:按位异或画精灵;撞到亮点就把 VF 置 1。 */
|
|
84
|
+
private __draw;
|
|
85
|
+
private __misc;
|
|
86
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 撤销 / 重做栈(纯逻辑,泛型)。
|
|
3
|
+
*
|
|
4
|
+
* 不是给某个画板专用的:任何「编辑 → 提交 → 后悔」的界面都能用(像素画板、看板、
|
|
5
|
+
* 表格编辑、表单草稿…),所以它只认泛型快照,不认识 canvas、颜色和数据结构。
|
|
6
|
+
*
|
|
7
|
+
* 约定:
|
|
8
|
+
* - `push(state)` 入栈并**清空 redo 栈** —— 从历史中间改一下,原来那条「未来」就作废了;
|
|
9
|
+
* - `undo()` / `redo()` 返回目标快照,到边界返回 `null`(调用方据此把按钮置灰);
|
|
10
|
+
* - 超过 `limit` 丢**最旧**的一条(默认 50:够用,且内存不会无限涨);
|
|
11
|
+
* - 变更通知带 `reason`(push / undo / redo / clear),页面按需刷新按钮态;
|
|
12
|
+
* - 快照的存取由调用方负责 `slice()` / 深拷贝 —— 栈只存引用,不做深拷贝(性能)。
|
|
13
|
+
*/
|
|
14
|
+
export declare type ICEHistoryReason = 'push' | 'undo' | 'redo' | 'clear';
|
|
15
|
+
export declare type ICEHistoryListener<T> = (model: ICEHistoryModel<T>, reason: ICEHistoryReason) => void;
|
|
16
|
+
export interface ICEHistoryOptions<T> {
|
|
17
|
+
/** 最多保留多少个**可撤销**的快照,默认 50 */
|
|
18
|
+
limit?: number;
|
|
19
|
+
/** 初始当前值(不占历史深度) */
|
|
20
|
+
initial?: T;
|
|
21
|
+
}
|
|
22
|
+
export declare class ICEHistoryModel<T> {
|
|
23
|
+
private limit;
|
|
24
|
+
/** 已提交的快照,最后一个就是当前值 */
|
|
25
|
+
private past;
|
|
26
|
+
private future;
|
|
27
|
+
private listeners;
|
|
28
|
+
constructor(options?: ICEHistoryOptions<T>);
|
|
29
|
+
getLimit(): number;
|
|
30
|
+
getCurrent(): T | null;
|
|
31
|
+
canUndo(): boolean;
|
|
32
|
+
canRedo(): boolean;
|
|
33
|
+
/** 栈深:undo = 还能撤销几步,redo = 还能重做几步。 */
|
|
34
|
+
getDepth(): {
|
|
35
|
+
undo: number;
|
|
36
|
+
redo: number;
|
|
37
|
+
};
|
|
38
|
+
push(state: T): void;
|
|
39
|
+
undo(): T | null;
|
|
40
|
+
redo(): T | null;
|
|
41
|
+
clear(initial?: T): void;
|
|
42
|
+
addChangeListener(listener: ICEHistoryListener<T>): () => void;
|
|
43
|
+
private notify;
|
|
44
|
+
}
|
|
45
|
+
export default ICEHistoryModel;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
export interface ICEPixelModelOptions {
|
|
2
|
+
rows?: number;
|
|
3
|
+
cols?: number;
|
|
4
|
+
/** 调色板(`#rgb` / `#rrggbb`),下标就是画布上存的值;不传给一组默认色 */
|
|
5
|
+
palette?: string[];
|
|
6
|
+
/** 背景色下标,默认 0 */
|
|
7
|
+
background?: number;
|
|
8
|
+
/** 最多能撤销几步,默认 50 */
|
|
9
|
+
limit?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface ICEPixelSVGOptions {
|
|
12
|
+
/** 每个像素在 SVG 里占多少单位,默认 16 */
|
|
13
|
+
cellSize?: number;
|
|
14
|
+
/** 是否画一层背景矩形,默认 true */
|
|
15
|
+
background?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare type ICEPixelListener = (model: ICEPixelModel) => void;
|
|
18
|
+
/** 默认调色板:Bootstrap 语义色 + 黑白灰(够画像素画,也不刺眼)。 */
|
|
19
|
+
export declare const ICE_PIXEL_DEFAULT_PALETTE: string[];
|
|
20
|
+
/** `#rgb` / `#rrggbb` → [r, g, b];认不出来当黑色(画布上永远有个确定的结果)。 */
|
|
21
|
+
export declare function icePixelParseColor(color: string): [number, number, number];
|
|
22
|
+
export declare class ICEPixelModel {
|
|
23
|
+
private rows;
|
|
24
|
+
private cols;
|
|
25
|
+
private palette;
|
|
26
|
+
private background;
|
|
27
|
+
private cells;
|
|
28
|
+
private history;
|
|
29
|
+
private listeners;
|
|
30
|
+
private dirty;
|
|
31
|
+
constructor(options?: ICEPixelModelOptions);
|
|
32
|
+
getRows(): number;
|
|
33
|
+
getCols(): number;
|
|
34
|
+
getPalette(): string[];
|
|
35
|
+
getBackground(): number;
|
|
36
|
+
/** 画布副本(行优先)。 */
|
|
37
|
+
getCells(): number[];
|
|
38
|
+
getPixel(row: number, col: number): number;
|
|
39
|
+
/** 有没提交的改动(页面据此把「保存 / 导出」按钮点亮)。 */
|
|
40
|
+
isDirty(): boolean;
|
|
41
|
+
canUndo(): boolean;
|
|
42
|
+
canRedo(): boolean;
|
|
43
|
+
getHistoryDepth(): {
|
|
44
|
+
undo: number;
|
|
45
|
+
redo: number;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* 画一个像素。返回「真的改了」—— 越界、颜色非法、值没变都返回 false,
|
|
49
|
+
* 这样页面可以放心地在拖动回调里调用它(不会刷出一堆无意义的重绘)。
|
|
50
|
+
*/
|
|
51
|
+
setPixel(row: number, col: number, color: number): boolean;
|
|
52
|
+
/** Bresenham 直线(铅笔拖动、直线工具都走它)。 */
|
|
53
|
+
drawLine(row0: number, col0: number, row1: number, col1: number, color: number): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* 直线经过的格子坐标(Bresenham)。
|
|
56
|
+
* 单独暴露出来是给**预览**用的:拖直线 / 拖矩形时先拿坐标点亮高亮层,松手才真正落笔。
|
|
57
|
+
*/
|
|
58
|
+
getLineCells(row0: number, col0: number, row1: number, col1: number): Array<[number, number]>;
|
|
59
|
+
/** 矩形**描边**经过的格子坐标(和 drawRect 同一套口径,供预览用)。 */
|
|
60
|
+
getRectCells(row0: number, col0: number, row1: number, col1: number): Array<[number, number]>;
|
|
61
|
+
/** 矩形描边(只画框,不填内部)。 */
|
|
62
|
+
drawRect(row0: number, col0: number, row1: number, col1: number, color: number): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* 四邻域油漆桶(迭代版,不用递归 —— 大画布上递归会爆栈)。
|
|
65
|
+
* 目标和填充色相同、或起点越界时返回 false。
|
|
66
|
+
*/
|
|
67
|
+
fill(row: number, col: number, color: number): boolean;
|
|
68
|
+
/** 整块刷成某个颜色(清空 / 填充背景)。 */
|
|
69
|
+
clear(color?: number): boolean;
|
|
70
|
+
/** 换尺寸:重新铺一块空画布(旧内容不留,历史也重开)。 */
|
|
71
|
+
resize(rows: number, cols: number): void;
|
|
72
|
+
/** 把当前画布落一次历史(一次笔画 / 一次图形操作调用一次)。返回是否真的入了栈。 */
|
|
73
|
+
commit(): boolean;
|
|
74
|
+
undo(): boolean;
|
|
75
|
+
redo(): boolean;
|
|
76
|
+
toSVG(options?: ICEPixelSVGOptions): string;
|
|
77
|
+
/** 给 `new ImageData(rgba, cols * scale, rows * scale)` 用的 RGBA 数组。 */
|
|
78
|
+
toRGBA(scale?: number): Uint8ClampedArray;
|
|
79
|
+
toJSON(): string;
|
|
80
|
+
/** 反序列化;坏数据抛错而不是静默给一块白板(导入失败得让人看见)。 */
|
|
81
|
+
static fromJSON(json: string): ICEPixelModel;
|
|
82
|
+
addChangeListener(listener: ICEPixelListener): () => void;
|
|
83
|
+
private __inside;
|
|
84
|
+
/** 非法下标一律当背景:画布上永远只有调色板里真实存在的颜色。 */
|
|
85
|
+
private __normalizeColor;
|
|
86
|
+
private __restore;
|
|
87
|
+
private notify;
|
|
88
|
+
}
|
|
89
|
+
export default ICEPixelModel;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ice-web-components",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Swing-style Canvas UI components built on ice-render.",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"qa:workbench": "node scripts/qa-workbench.mjs",
|
|
23
23
|
"qa:xp": "node scripts/qa-xp.mjs",
|
|
24
24
|
"qa:arcade": "node scripts/qa-arcade.mjs",
|
|
25
|
+
"qa:pixel": "node scripts/qa-pixel.mjs",
|
|
25
26
|
"docs:api": "node scripts/gen-docs.mjs",
|
|
26
27
|
"docs:check": "node scripts/check-docs.mjs",
|
|
27
28
|
"docs": "npm run docs:api && npm run docs:check",
|