cfc-desktop-pet(Tauri v2 桌面宠物)实现宠物的精细化动作,比如对于玄凤鹦鹉,要能够走路,也能够跳着走。鼠标放在鹦鹉上时变成手指的样式,放在胸部 5 秒钟,鹦鹉会迈到手指上,然后手指鼠标移到哪儿,鹦鹉就跟到哪儿。鼠标抖动一下鹦鹉会飞到屏幕的任意地方。
拆解为 4 个行为:
src/DefaultPets/{xibao,fubao}/ 只有 pet-config.json,无任何图片,宠物实际渲染 emoji 兜底形象。因此不依赖精灵帧,改用「整体位移 / 弹跳 / 挤压拉伸 / 旋转 / 扑腾」等程序化姿态 + 移动窗口实现动作。宠物所在窗口仅 200×200 且透明置顶,装不下跨屏走动/飞行——所以"走/跳/飞/跟随"必须移动窗口本身。窗口位置只能由 Rust 侧修改,故分层:
| 层 | 职责 |
|---|---|
| Rust(移动者) | 轮询全局光标 cursor_position();按显示器 current_monitor().work_area() 夹取边界;移动窗口 set_position();抖动检测;驱动 locomotion 状态机;通过 pet://gait 事件把姿态种类下发给前端 |
| JS(渲染者) | 只渲染局部姿态(走路摆动、跳跃挤压、飞行扑腾、站手平衡),以及窗口内的悬停/胸部悬停判定与气泡 |
选用理由:避免 JS 每帧 invoke 跨进程调用造成卡顿(方案 B 被否),不引入物理/动画库(方案 C 被否,YAGNI)。
WebviewWindow::cursor_position() -> Result<PhysicalPosition<f64>>(全局光标,无需全局钩子 → 免 macOS 辅助功能权限)WebviewWindow::set_position(Position) / outer_position() / outer_size()WebviewWindow::current_monitor() -> Result<Option<Monitor>>Monitor::work_area() -> &PhysicalRect<i32,u32>(position + size)#pet(情绪动画,由 pet-engine.js 管理 .anim-*)内新增 #pet-body 包装层承载姿态动画(.pose-*)。两层 transform 独立叠加,互不覆盖。
#pet ← 情绪:.anim-wings_idle / .anim-clap …(pet-engine)
└── #pet-body ← 姿态:.pose-walk / .pose-hop / .pose-fly / .pose-perch(pet-pose)
└── #pet-img
└── #chest(脆弱热区:胸部 hover 判定)
└── #bubble
locomotion.rs(新)Idle(静立)→ 随机触发 AutoWalk/Hop;Perch(跟随)由命令进入;抖动触发 Fly。Perch 时直接 窗口 = 光标 - 锚点(锚点 = 宠物双脚在窗口内的坐标 ≈ (100, 200))。current_monitor().work_area(),保证整窗可见。Fly。Fly:约 600ms 内插值移动到工作区内随机目标点,落地回 Idle。pet://gait {kind, direction, phase}(kind ∈ idle/walk/hop/fly/perch)。pet://perch {active}(进入/退出跟随,供前端同步"是否站手")。state.rs(纯函数 + 单测)新增可单测的纯逻辑(不依赖 tauri 运行时):
detect_shake(samples, now) -> bool:方向反转 + 位移阈值。clamp_window_pos(pos, work_area, win_size) -> (i32,i32):把窗口左上角夹进工作区。pick_random_target(work_area, win_size, seed) -> (i32,i32):工作区内的随机落点(用简单 LCG,便于测试确定性)。app_state.rs / commands.rs / main.rsAppState 增 follow_cursor: AtomicBool(命令与循环共享)。start_perch(胸部悬停满 5s 时前端调用)→ 置 follow_cursor=true,emit pet://gait {kind:"perch"} + pet://perch {active:true}。stop_perch → 置 false,emit pet://perch {active:false}。main.rs:注册新命令;setup 内 locomotion::spawn(...) 启动循环(与 poller::spawn 并列)。index.html:新增 #pet-body 包装、#chest 热区;#pet { cursor: pointer }(手指);引入 pet-pose.js;#chest 定位在鹦鹉胸部(上方中区)。animations.css:新增 @keyframes walk_bob / hop / fly_flutter / perch_lean 与 .pose-* 类。pet-pose.js(新):监听 pet://gait 切换 #pet-body 的姿态类;监听 pet://perch 设置 window.__petPerched。bridge.js:cfcHost 增 startPerch() / stopPerch() → invoke('start_perch'|'stop_perch')。interactions.js:
#chest mouseenter 起 5s 计时 → cfcHost.startPerch();mouseleave 前取消;成功后面板气泡「飞到你手上~」。__petPerched 为真时跳过 drag_start(避免跟随与原生拖拽打架)。| 操作 | 结果 |
|---|---|
| 空闲 | 偶发 walk / hop(窗口横移;hop 带弹跳) |
| 鼠标移到鹦鹉上 | 光标变手指(pointer) |
| 悬停胸部 5 秒 | 迈到"手指"上 → 跟随鼠标(窗口跟随光标,双脚落在光标处) |
| 跟随中抖动鼠标 | fly → 飞到当前显示器工作区随机点 → 落地回 idle |
current_monitor() 工作区为准 clamp。set_ignore_cursor_events(鼠标穿透)与移动窗口互不影响。cargo test 覆盖 detect_shake / clamp_window_pos / pick_random_target,全绿。cargo check --all-targets 通过。.js node --check 通过。pet_config.animations.frames 接入)。