窗口切换
首先我们实现一个简单一点的功能:当按下 F11 时,将最下面那个窗口放到最上面。
在 bootpack.c 里加上对 F11 的处理就可以了,代码如下:
1 | if (i == 256 + 0x57 && shtctl->top > 2) { /* F11 */ |
注意第 0 层是背景,第 top 层是鼠标。所以所谓的最下面指的是第 1 层,最上面指的是第 top - 1 层。
看一下效果吧:
这里有意思的一点是按下 F11 没反应,然后去 14.4 节查看按键数值表,发现 F1 键到 F10 键的按键编码是 0x3b ~ 0x44,而 F11 键变成了 0x57。我猜测应该是键盘布局不同所以导致的,所以我就把代码改成了 0x44,也就是在按下 F10 的时候实现窗口切换的功能。
现在我们来实现用鼠标点击来切换窗口的功能,当鼠标点击画面的某个地方时,按照从上到下的顺序,判断鼠标的位置落在哪个图层的范围内,并且还需要确保该位置不是透明色区域。程序如下:
1 | if ((mdec.btn & 0x01) != 0) { |
运行一下试试看:
移动窗口
现在我们来实现窗口的移动。当鼠标左键点击窗口时,如果点击位置位于窗口的标题栏区域,则进入「窗口移动模式」,使窗口的位置追随鼠标指针的移动,当放开鼠标左键时,退出「窗口移动模式」,返回通常模式。
这个思路其实和我在第 14 天中提到的思路还是很像的,当时我写的代码如下:
1 | if ((mdec.btn & 0x01) != 0 |
这里作者的做法如下,其中 mmx 和 mmy 记录的是移动之前的坐标:
1 | if ((mdec.btn & 0x01) != 0) { |
我们运行一下看看效果:
用鼠标关闭窗口
现在我们实现点击「×」来关闭窗口的功能,处理方法与刚刚类似,代码如下:
1 | if (sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19) { |
效果如下:
将输入切换到应用程序窗口
我们规定在按下 Tab 键时将键盘输入切换到当前输入窗口下面一层的窗口中,若当前窗口为最下层,则切换到最上层窗口。
这里我们用 key_win 来存放当前处于输入模式的窗口地址。
当窗口处于输入模式时被关闭的话,我们让系统自动切换到最上层的窗口。
为了分辨窗口是不是由应用程序生成的,我们需要通过 SHEET 结构中的 flags 成员进行判断(以 0x10 比特位进行区分)。此外,只有命令行窗口需要控制光标的 ON/OFF,应用程序窗口不需要,这一区别也是通过 flags 来进行判断的(以 0x20 比特位进行区分)。
修改后的代码如下:
1 | if (key_win->flags == 0) { /* 输入窗口被关闭 */ |
上面的代码中调用了 keywin_on 和 keywin_off 两个函数,功能是控制窗口标题栏的颜色和 task_a 窗口的光标,代码如下:
1 | int keywin_off(struct SHEET *key_win, struct SHEET *sht_win, int cur_c, int cur_x) |
change_wtitle8 函数的功能是改变窗口标题栏的颜色,代码如下:
1 | void change_wtitle8(struct SHEET *sht, char act) |
然后对 cmd_app 也进行修改,通过 flags 的 0x10 比特位来判断应用程序结束时是否自动关闭窗口。修改部分如下:
1 | for (i = 0; i < MAX_SHEETS; i++) { |
同时我们需要修改 hrb_api,在打开窗口的地方启用自动关闭窗口的功能,将 flags 或上 0x10。代码如下:
1 | if (edx == 5) { |
现在我们运行一下看看效果:
### 用鼠标切换输入窗口
刚才我们实现了用 Tab 键来切换输入窗口,现在我们实现用鼠标也能够切换。我们在处理鼠标数据的部分中加入如下代码即可:
1 | if (sht != key_win) { |
运行一下看看效果:
定时器 API
这里我们要实现四个 API,如下所示:
获取定时器(alloc)
寄存器 | 值 |
---|---|
EDX | 16 |
EAX | 定时器句柄(由操作系统返回) |
设置定时器的发送数据(init)
寄存器 | 值 |
---|---|
EDX | 17 |
EBX | 定时器句柄 |
EAX | 数据 |
定时器时间设定(set)
寄存器 | 值 |
---|---|
EDX | 18 |
EBX | 定时器句柄 |
EAX | 时间 |
释放定时器(free)
寄存器 | 值 |
---|---|
EDX | 19 |
EBX | 定时器句柄 |
程序如下:
1 | int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax) |
应用程序编写如下:
1 | _api_alloctimer: ; int api_alloctimer(void); |
然后我们在主程序中加入以下代码,就可以启动并显示出我们的定时器了。
1 | void HariMain(void) |
这里的 128 是定时器超时时产生的值,如果不是这个值,表示用户按下了其他案件,应用程序结束退出。
运行一下,效果如下:
取消定时器
刚刚的定时器还有一个问题,就是当定时器超时时会向任务发送事先设置的数据,但如果此时应用程序已经结束了的话,定时器的数据就会被发送到命令行窗口。要解决这个问题,我们需要取消待机中的定时器,从而在应用程序结束的同时取消定时器。
用于取消指定定时器的函数如下:
1 | int timer_cancel(struct TIMER *timer) |
然后我们编写在应用程序结束时取消全部定时器的函数,其中 flags2 是一个标记,用来区分该定时器是否需要在应用程序结束时自动取消。
1 | struct TIMER *timer_alloc(void) |
注意要将应用程序所申请的定时器的 flags2 设为 1。
最后我们编写一个函数,来取消应用程序结束时不需要的定时器。
1 | void timer_cancelall(struct FIFO32 *fifo) |
我们来看看效果吧!
大功告成了。