# eduwin 画布与精灵 API

## 画布

```c
HWND create_canvas(HWND parent, int x, int y, int w, int h);
```

创建一个可绘制的画布控件，使用 GDI+ 作为后端。画布维护一个离屏位图，所有绘图操作都在位图上进行，窗口重绘时自动刷新。

> **重要：所有绘图操作必须在 `show_window` 之后调用！**
>
> `show_window` 会触发 `WM_SIZE` 消息，画布会销毁当前离屏位图并按实际客户区尺寸重建。如果在 `show_window` 之前绘图，内容会被清空。

---

## 绘图状态设置

```c
void canvas_clear(HWND canvas, int r, int g, int b, int a);
void canvas_set_pen(HWND canvas, int r, int g, int b, int a, int width);
void canvas_set_brush(HWND canvas, int r, int g, int b, int a);
void canvas_set_font(HWND canvas, const WCHAR *family, int size);
```

`r` / `g` / `b` 为颜色的红、绿、蓝分量，范围 0–255。`a` 为透明通道（alpha），0 完全透明，255 完全不透明。

- `canvas_clear` 用指定颜色填充整个画布（清除原有内容）
- `canvas_set_pen` 设置轮廓画笔颜色、透明度和线宽（默认黑色 1px 不透明）
- `canvas_set_brush` 设置填充画刷颜色和透明度（默认白色不透明）
- `canvas_set_font` 设置文本字体和字号（默认 Microsoft YaHei 14）

---

## 绘图操作

```c
void canvas_draw_line(HWND canvas, int x1, int y1, int x2, int y2);
void canvas_draw_rect(HWND canvas, int x, int y, int w, int h);
void canvas_fill_rect(HWND canvas, int x, int y, int w, int h);
void canvas_draw_ellipse(HWND canvas, int x, int y, int w, int h);
void canvas_fill_ellipse(HWND canvas, int x, int y, int w, int h);
void canvas_draw_text(HWND canvas, const WCHAR *text, int x, int y, int r, int g, int b, int a, int size);
void canvas_draw_image(HWND canvas, void *image, int x, int y, int w, int h);
```

- `canvas_draw_line` / `canvas_draw_rect` / `canvas_draw_ellipse` — 用当前画笔绘制轮廓
- `canvas_fill_rect` / `canvas_fill_ellipse` — 用当前画刷填充
- `canvas_draw_text` — 在指定位置绘制文本，`r g b a` 为文字颜色和透明度，`size` 为字号（传 0 使用 `canvas_set_font` 设定的字号）
- `canvas_draw_image` — 绘制由 `load_image_from_file` 返回的图像

```c
HWND cv = create_canvas(win, 10, 10, 400, 300);

canvas_clear(cv, 255, 255, 255, 255);               // 白色不透明背景

canvas_set_pen(cv, 0, 0, 255, 255, 3);              // 蓝色 3px 不透明画笔
canvas_draw_rect(cv, 20, 20, 100, 80);              // 矩形边框

canvas_set_brush(cv, 255, 200, 200, 255);            // 粉色不透明画刷
canvas_fill_ellipse(cv, 140, 20, 100, 80);          // 填充椭圆

canvas_draw_line(cv, 20, 120, 300, 120);             // 直线

canvas_draw_text(cv, L"Hello 画布", 20, 140,         // 文字
                 0, 0, 0, 255, 20);
```

---

## 局部清除

```c
void canvas_clear_rect(HWND canvas, int x, int y, int w, int h, int r, int g, int b, int a);
```

用颜色填充指定矩形区域（局部清除），`a` 为透明度。

```c
// 在灰色区域中间挖一个透明洞
canvas_set_brush(canvas, 200, 200, 200, 255);
canvas_fill_rect(canvas, 10, 10, 200, 100);
canvas_clear_rect(canvas, 50, 30, 100, 40, 100, 200, 50, 255);
```

---

## 文件输出

```c
void canvas_save_image(HWND canvas, const WCHAR *filepath);
```

将画布的当前内容保存为图像文件（根据扩展名自动选择编码器，支持 png/jpg/bmp 等）。

```c
canvas_save_image(cv, L"output.png");   // 保存到文件
```

---

## 游戏开发扩展

### 帧循环

```c
void canvas_start_timer(HWND canvas, int fps, void (*callback)(void *), void *data);
void canvas_stop_timer(HWND canvas);
```

`canvas_start_timer` 启动帧循环，`fps` 为每秒回调次数，`callback` 每帧调用一次。`canvas_stop_timer` 停止帧循环。

### 键盘输入

```c
int canvas_is_key_down(HWND canvas, int vkey);
void canvas_on_key(HWND canvas, void (*down)(int), void (*up)(int));
```

`canvas_is_key_down` 实时检测按键是否按住，`vkey` 为虚拟键码（如 `VK_LEFT`、`'A'`）。`canvas_on_key` 注册按键按下 / 释放事件回调。

### 鼠标输入

```c
void canvas_on_mouse(HWND canvas, void (*move)(int, int), void (*down)(int, int, int), void (*up)(int, int, int));
int canvas_get_mouse_x(HWND canvas);
int canvas_get_mouse_y(HWND canvas);
int canvas_is_mouse_down(HWND canvas, int btn);
```

`canvas_on_mouse` 注册鼠标移动 / 按下 / 释放回调。

- `move(x, y)` — 鼠标移动
- `down(btn, x, y)` — 鼠标按下，`btn` 0=左键, 1=中键, 2=右键
- `up(btn, x, y)` — 鼠标释放

`canvas_get_mouse_x/y` 获取鼠标当前坐标。`canvas_is_mouse_down` 检测鼠标是否按住（`btn` 0=左, 1=右）。

> **坐标注意：** 画布鼠标回调中的 `x, y` 是**画布客户区坐标**。如果需要与 `show_context_menu` 等需要屏幕坐标的函数配合使用，必须用 `ClientToScreen` 转换：
>
> ```c
> void on_mouse_down(int btn, int x, int y)
> {
>     POINT pt = {x, y};
>     ClientToScreen(canvas, &pt);
>     show_context_menu(ctx_menu, canvas, pt.x, pt.y);
> }
> ```

### 画布尺寸

```c
int canvas_get_width(HWND canvas);
int canvas_get_height(HWND canvas);
```

获取画布的实际像素尺寸。

### 游戏循环示例

```c
void game_tick(void *data)
{
    canvas_clear(canvas, 0, 0, 0, 255);

    // WASD 控制方块
    static float px = 100, py = 100;
    if (canvas_is_key_down(canvas, 'W')) py -= 3;
    if (canvas_is_key_down(canvas, 'S')) py += 3;
    if (canvas_is_key_down(canvas, 'A')) px -= 3;
    if (canvas_is_key_down(canvas, 'D')) px += 3;

    canvas_set_brush(canvas, 0, 200, 0, 255);
    canvas_fill_rect(canvas, (int)px, (int)py, 40, 40);

    canvas_draw_text(canvas, L"按 ESC 退出", 5, 5, 128, 128, 128, 255, 14);
}

// 在 main 中：
canvas_start_timer(canvas, 60, game_tick, NULL);
canvas_on_key(canvas, NULL, NULL);  // 确保画布获得焦点
```

---

## 精灵系统

精灵（Sprite）是画布上可独立控制的图形对象。所有精灵内部都统一为位图（`GpBitmap`）处理——形状创建时自动渲染为位图，图片从文件加载后也是位图。后续所有操作（颜色叠加、变换等）都直接作用于位图数据。

### 形状类型常量

```c
#define SHAPE_RECT      0   /* 矩形 */
#define SHAPE_ELLIPSE   1   /* 椭圆/圆 */
#define SHAPE_TRIANGLE  2   /* 三角形（朝上） */
#define SHAPE_DIAMOND   3   /* 菱形 */
#define SHAPE_PENTAGON  4   /* 正五边形 */
#define SHAPE_HEXAGON   5   /* 正六边形 */
#define SHAPE_STAR      6   /* 五角星 */
#define SHAPE_ARROW     7   /* 箭头（朝右） */
#define SHAPE_PIE       8   /* 扇形（缺右上角） */
```

### 精灵创建与销毁

```c
Sprite *sprite_create_from_shape(int type, int w, int h);
Sprite *sprite_create_from_file(const WCHAR *filepath);
Sprite *sprite_create_from_image(void *image);
Sprite *sprite_clone(Sprite *s);
void sprite_destroy(Sprite *s);
```

- `sprite_create_from_shape(type, w, h)` 创建指定形状和大小的精灵，`type` 为上述 `SHAPE_*` 常量之一
- `sprite_create_from_file` 从图片文件创建精灵，尺寸自动匹配
- `sprite_create_from_image` 从 `load_image_from_file` 返回的图像创建精灵（内部克隆副本，不直接引用外部指针）
- `sprite_clone` 克隆精灵，内部共享原始图片备份（`origin_image`），克隆体有独立的工作副本（`image`）
- `sprite_destroy` 销毁精灵并释放资源

### 精灵变换

```c
void sprite_set_pos(Sprite *s, float x, float y);
void sprite_move(Sprite *s, float dx, float dy);
void sprite_set_scale(Sprite *s, float sx, float sy);
void sprite_set_rotation(Sprite *s, float degrees);
void sprite_set_color(Sprite *s, int r, int g, int b, int a);
void sprite_set_image(Sprite *s, void *image);
```

- `sprite_set_pos` 设置精灵左上角位置
- `sprite_move` 相对当前位置移动
- `sprite_set_scale` 缩放精灵（1.0 为原始大小）
- `sprite_set_rotation` 旋转精灵（角度制），绕原点（默认中心）旋转
- `sprite_set_color` 设置精灵的整体颜色叠加（**正片叠底 Multiply 混色模式**，透明像素不受影响）
- `sprite_set_image` 更换精灵的图像（内部克隆为新副本，并更新原始图备份）

### 精灵属性读取

```c
float sprite_get_x(Sprite *s);
float sprite_get_y(Sprite *s);
float sprite_get_w(Sprite *s);
float sprite_get_h(Sprite *s);
float sprite_get_sx(Sprite *s);
float sprite_get_sy(Sprite *s);
```

### 精灵绘制与碰撞

```c
void sprite_draw(HWND canvas, Sprite *s);
int sprite_collide(Sprite *a, Sprite *b);
int sprite_hit_test(Sprite *s, float px, float py);
```

`sprite_draw` 将精灵绘制到指定画布，自动处理位置、缩放、旋转。`sprite_collide` AABB 碰撞检测，传入两个 `Sprite*`，返回 1 表示碰撞。`sprite_hit_test` 检测点 `(px, py)` 是否在精灵矩形内（含缩放）。

### 精灵事件

```c
void on_sprite_click(HWND canvas, Sprite *s, void (*cb)(Sprite *, void *), void *data);
void on_sprite_down(HWND canvas, Sprite *s, void (*cb)(Sprite *, void *), void *data);
void on_sprite_up(HWND canvas, Sprite *s, void (*cb)(Sprite *, void *), void *data);
void on_sprite_hover(HWND canvas, Sprite *s, void (*cb)(Sprite *, void *), void *data);
```

为精灵注册鼠标事件回调。`click` 在精灵上点击时触发，`down`/`up` 在精灵上按下/释放鼠标时触发，`hover` 在鼠标悬停在精灵上时持续触发。销毁精灵时自动清理事件。

### 精灵颜色叠加说明

`sprite_set_color` 采用**正片叠底（Multiply）**混色模式，每次调用都从原始备份图（`origin_image`）重新克隆工作副本再执行混色，因此**多次调用不会累积加深颜色**。

颜色参数 `r/g/b` 控制色调，`a` 控制强度（0=完全透明/无效果，255=完全叠加）：
- `sprite_set_color(s, 255, 0, 0, 120)` 叠加红色调
- `sprite_set_color(s, 255, 255, 255, 255)` 恢复原始颜色

### Sprite 结构体字段

Sprite 的公有字段可以直接读写：

```c
sprite->x = 100;       /* 直接设坐标 */
sprite->y = 200;
sprite->visible = 0;   /* 隐藏 */
sprite->ox = 0;        /* 原点移到左上角 */
sprite->oy = 0;
```

内部字段（不应直接修改）：
- `image` — 当前绘制用的位图（工作副本）
- `origin_image` — 原始图片备份，每次 `set_color` 从此重新克隆
- `is_owner` — `1`=销毁时释放全部资源，`0`=克隆体仅释放自己的工作副本
