跳转至

应用的图形与事件(2) — 眼球、绘图板、计时器与打方块

这一章讲什么: 应用的事件世界从「一个 Ctrl+Q」扩成三族: 鼠标移动(眼球盯着光标转)、鼠标按键(按下拖动画线的绘图软件)、 定时器超时(应用自己的计时器,不再借用系统的)。 原料一够,立刻造两件作品:旋转立方体(定时器控制帧间隔 = 动画原理) 和打方块(鼠标当拍子的小游戏)——操作系统的价值,最终要用「能写出什么应用」来衡量。

1. 先看现象:三个应用,三种事件

主线例子:

  eye(眼球):ReadEvent 收到 kMouseMove(x, y, dx, dy, buttons)
      → 算眼球中心到光标的向量 → atan2 得方向 → 眼珠偏移 min(距离, 半径)
      → 重绘 → 眼珠看起来一直盯着光标
  paint(绘图板):kMouseButton 记录按下/松开;按下期间的 kMouseMove 逐个画点成线
      前提修复:点击窗口任意处都会进拖动模式 → 改成「只有点在标题栏(kTopLeftMargin 内)
      才设 drag_layer_id_」,不然画一笔窗口先跑了
  timer(计时器):CreateTimer(mode, value) → 到点收到 kTimerTimeout
      系统改造:Timer 类加「任务 ID」字段,超时消息发给它而不是固定发主任务;
      timer_value 取负 —— 系统自己的定时器是 ≥0,应用的是 <0,ReadEvent 里正值的直接丢掉
  cube / block:定时器帧驱动 + 鼠标输入 = 动画与游戏

2. 顶层全景

  23.1 鼠标移动事件:SendMouseMessage 发活动窗口任务;eye 应用(向量+atan2)
  23.2 鼠标按键事件:kMouseButton;paint 应用;拖动语义修正为「仅标题栏」
  23.3 应用定时器:Timer 加任务 ID;CreateTimer(oneshot, ms);
       负 timer_value 约定;timer 应用「xx msecs elapsed!」
  23.4 动画:帧间隔由定时器控制;旋转立方体(三维投影)
  23.5 打方块:拍子跟鼠标、球反弹、消砖块——全书最大的「成品应用」

  图说:事件三族齐备,API 的「生态效应」开始显形。

3. 核心原理

3.1 鼠标事件进队列

鼠标移动的通路早已存在(USB 驱动 → Mouse::OnInterrupt → 拖动窗口), 本章加的是抄送:SendMouseMessage 把「位置、相对位移、按键状态」发给 活动窗口所属的任务的队列;ReadEvent 把队列里的消息翻译成 kMouseMove 事件1。 eye 应用演示了事件的典型消费:光标坐标(相对窗口左上角)→ 减窗口中心得向量 → atan2 求方向角、min(距离, 半径) 求偏移 → 画眼珠2。

3.2 按键事件与拖动语义的修正

kMouseButton 事件带来按下/松开沿;paint 应用:按下后把移动串成线3。 但点击窗口任意位置都会进入拖动模式,画一笔窗口先跑了——修法: 只有按下点落在标题栏区域(kTopLeftMargin 之内)才设 drag_layer_id_4。 这是一个被应用需求逼出来的 UX 修正:窗口系统自己的语义(点哪都能拖)与 应用的语义(内容区归应用)冲突时,界限画在标题栏。

3.3 应用定时器:任务 ID 与负值约定

系统定时器原本写死「超时通知主任务」;应用的定时器显然该通知应用所在的终端任务。 改法干脆:Timer 类加一个任务 ID 字段,超时消息发给它5。 CreateTimer(mode, value) 系统调用:oneshot 模式,毫秒计; timer_value 取负——系统自己的定时器(光标闪烁等)是 ≥0,应用的是 <0, ReadEvent 见到正值的超时事件直接忽略(不该让应用拿到)6。 这个「符号位当命名空间」的约定土而有效,还顺手修了一处旧账: 任务切换定时器用的是 int 最小值,改成正值后约定才统一7。

3.4 动画与游戏:定时节拍

动画的原理一句话:快速切换静态画面,帧间隔由定时器控制——太快太慢都不正常8。 旋转立方体:三维顶点绕轴旋转 → 投影到二维 → 连线; 帧与帧之间用定时器卡节奏9。 打方块(23.5):球、拍子、砖块三类对象;拍子跟鼠标横移,球撞边/撞拍子反弹, 撞砖块消行加分——这是全书最大的成品应用,所有已建的机制(窗口、事件、定时器、 绘制)在它身上合流10。

4. 作者的判断与证据

判断:事件的默认路由是「活动窗口的任务」。 多个任务都想要鼠标怎么办?本章的选择: 只发活动窗口——简单且符合直觉;广播与按需登记留作将来。

判断:用应用检验 API。 每加一族事件立刻配一个演示应用(eye/paint/timer), 再叠两个「作品级」应用(cube/block)。API 的设计质量,由应用写起来的顺手程度裁决。

证据: 眼珠跟随光标的截图、paint 的涂鸦、计时器的毫秒输出、旋转立方体与打方块的运行图。

5. 边界与局限

  • 鼠标事件只发给活动窗口:非活动窗口的应用收不到悬停/滚动;
  • 定时器只有 oneshot 一种模式,周期模式靠应用自己续杯;
  • 负值约定是土办法:一任务多命名空间再扩就要正经的 ID 方案;
  • paint 的线靠移动事件密度:鼠标动得快线会断(没有插值),教学演示可忍。

6. 可带走的

本章主线例子,一行写完: 鼠标移动→eye、按键→paint(标题栏才拖窗)、CreateTimer→kTimerTimeout(负值区分系统定时器)→ 定时器节拍驱动 cube 动画与打方块。

  1. 事件通路 = 旧消息管道 + 新的事件翻译层,一层一层往上贴;
  2. 鼠标事件至少带位置、相对位移、按键状态三样;
  3. UX 冲突要有明确界限:窗口的内容区归应用,标题栏归窗口管理器;
  4. 定时器要发给发起任务:Timer 记任务 ID;
  5. 「符号位当命名空间」(负 timer_value)是土而有效的约定;
  6. 动画 = 定时器卡帧间隔;游戏 = 事件 + 定时器 + 绘制的合流。

7. 原文地图

主题 原书章 原文位置
鼠标移动事件与抄送 23.1 鼠标输入(osbook_day23a) text/150-ch23-01-23-1-osbook-day23a.txt:4(搜「每次鼠标移动都会产生一个事件」) · text/150-ch23-01-23-1-osbook-day23a.txt:29(搜「SendMouseMessage」)
eye 应用向量与 atan2 23.1 鼠标输入(osbook_day23a) text/150-ch23-01-23-1-osbook-day23a.txt:74(搜「DrawEye」) · text/150-ch23-01-23-1-osbook-day23a.txt:93(搜「atan2」)
kMouseButton 与 paint 23.2 绘图软件(osbook_day23b) text/151-ch23-02-23-2-osbook-day23b.txt:22(搜「捕捉按下按钮」) · text/151-ch23-02-23-2-osbook-day23b.txt:32(搜「kMouseButton」)
标题栏才拖动 23.2 绘图软件(osbook_day23b) text/151-ch23-02-23-2-osbook-day23b.txt:41(搜「拖动模式」) · text/151-ch23-02-23-2-osbook-day23b.txt:45(搜「kTopLeftMargin」)
Timer 任务 ID 改造 23.3 计时器命令(osbook_day23c) text/152-ch23-03-23-3-osbook-day23c.txt:29(搜「任务被固定为1」) · text/152-ch23-03-23-3-osbook-day23c.txt:48(搜「任务ID」)
CreateTimer 与负值约定 23.3 计时器命令(osbook_day23c) text/152-ch23-03-23-3-osbook-day23c.txt:66(搜「CreateTimer」) · text/152-ch23-03-23-3-osbook-day23c.txt:82(搜「取相反数」) · text/152-ch23-03-23-3-osbook-day23c.txt:104(搜「大于0」)
动画原理与旋转立方体 23.4 动画(osbook_day23d) text/153-ch23-04-23-4-osbook-day23d.txt:13(搜「看起来会动」) · text/153-ch23-04-23-4-osbook-day23d.txt:19(搜「过渡」) · text/153-ch23-04-23-4-osbook-day23d.txt:28(搜「立方体」)
打方块 23.5 打方块游戏(osbook_day23e) text/154-ch23-05-23-5-osbook-day23e.txt:3(搜「kScale」——常量与结构说明;中文段落散见同节)

  1. 出处:「23.1 鼠标输入」(text/150-ch23-01-23-1-osbook-day23a.txt:4,搜「每次鼠标移动都会产生一个事件」;:30,搜「SendMouseMessage」)。 ↩

  2. 出处:「23.1 鼠标输入」(text/150-ch23-01-23-1-osbook-day23a.txt:74,搜「DrawEye」;:93,搜「atan2」)。 ↩

  3. 出处:「23.2 绘图软件」(text/151-ch23-02-23-2-osbook-day23b.txt:22,搜「捕捉按下按钮」;:32,搜「kMouseButton」)。 ↩

  4. 出处:「23.2 绘图软件」(text/151-ch23-02-23-2-osbook-day23b.txt:41,搜「拖动模式」;:45,搜「kTopLeftMargin」)。 ↩

  5. 出处:「23.3 计时器命令」(text/152-ch23-03-23-3-osbook-day23c.txt:29,搜「任务被固定为1」;:48,搜「任务ID」)。 ↩

  6. 出处:「23.3 计时器命令」(text/152-ch23-03-23-3-osbook-day23c.txt:66,搜「CreateTimer」;:82,搜「取相反数」;:104,搜「大于0」)。 ↩

  7. 出处:「23.3 计时器命令」(text/152-ch23-03-23-3-osbook-day23c.txt:87,搜「numeric_limits」——脚注处说明任务切换定时器旧值因此统一)。 ↩

  8. 出处:「23.4 动画」(text/153-ch23-04-23-4-osbook-day23d.txt:13,搜「看起来会动」;:19,搜「过渡」;:28,搜「立方体」)。 ↩

  9. 出处:「23.4 动画」(text/153-ch23-04-23-4-osbook-day23d.txt:28,搜「立方体」;:30,搜「サイコロ」——日文残页,三维旋转的说明)。 ↩

  10. 出处:「23.5 打方块游戏」(text/154-ch23-05-23-5-osbook-day23e.txt:3,搜「kScale」;:6,搜「kMargin」;:7,搜「kCanvasSize」)。 ↩