本篇内容继续围绕显示展开。首先对鼠标显示做了些优化,鼠标箭头在到达画面边缘时能够实现部分隐藏;接下来制作了窗口,实现了窗口显示;最后还在窗口的基础上实现了计数器,显示计数的变化并消除闪烁的问题。
1. 画面边缘隐藏部分鼠标
首先优化一下鼠标显示,在鼠标箭头移动到画面边缘时,隐藏部分鼠标箭头。主程序中原代码如下:
if (mx > binfo->scrnx - 16) {
mx = binfo->scrnx - 16;
}
if (my > binfo->scrny - 16) {
my = binfo->scrny - 16;
}
修改为如下代码:
if (mx > binfo->scrnx - 1) {
mx = binfo->scrnx - 1;
}
if (my > binfo->scrny - 1) {
my = binfo->scrny - 1;
}
原理也比较简单,显示效果如下:
可以看出鼠标箭头实现了在画面边缘的部分隐藏,但移出画面之外的部分又产生了其他异常。这里是与图层的刷新有关,只要对于画面之外的部分不进行刷新,就不会产生上面的问题了。修改sheet_refreshsub函数如下:
void sheet_refreshsub(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1)
{
int h, bx, by, vx, vy, bx0, by0, bx1, by1;
unsigned char *buf, c, *vram = ctl->vram;
struct SHEET *sht;
for (h = 0; h <= ctl->top; h++) {
sht = ctl->sheets[h];
buf = sht->buf;
bx0 = vx0 - sht->vx0;
by0 = vy0 - sht->vy0;
bx1 = vx1 - sht->vx0;
by1 = vy1 - sht->vy0;
/* 如果超出了范围则进行修正 */
if (bx0 < 0) { bx0 = 0; }
if (by0 < 0) { by0 = 0; }
if (bx1 > sht->bxsize) { bx1 = sht->bxsize; }
if (by1 > sht->bysize) { by1 = sht->bysize; }
for (by = by0; by < by1; by++) {
vy = sht->vy0 + by;
for (bx = bx0; bx < bx1; bx++) {
vx = sht->vx0 + bx;
c = buf[by * sht->bxsize + bx];
if (c != sht->col_inv) {
vram[vy * ctl->xsize + vx] = c;
}
}
}
}
return;
}
这样修改之后,鼠标在画面边缘的显示就正常多了
2. 窗口显示
在进入下一步 制作窗口之前,先进行一个小优化。
之前图层控制的数据结构SHTCTL *ctl,在每次进行图层相关的操作时,都需要将ctl指针作为参数传入进去。为了简化,将这一指针加入图层信息描述的结构体中,这样每次操作图层时从图层信息结构体中获取该指针即可,不需要再作为参数单独传入了。
struct SHEET {
unsigned char *buf;
int bxsize, bysize, vx0, vy0, col_inv, height, flags;
struct SHTCTL *ctl; /* 此处增加图层控制指针 */
};
在初始化函数shtctl_init中,需要给此指针赋值:
struct SHTCTL *shtctl_init(struct MEMMAN *memman, unsigned char *vram, int xsize, int ysize)
{
struct SHTCTL *ctl;
int i;
ctl = (struct SHTCTL *) memman_alloc_4k(memman, sizeof (struct SHTCTL));
if (ctl == 0) {
goto err;
}
ctl->vram = vram;
ctl->xsize = xsize;
ctl->ysize = ysize;
ctl->top = -1;
for (i = 0; i < MAX_SHEETS; i++) {
ctl->sheets0[i].flags = 0;
ctl->sheets0[i].ctl = ctl;
}
err:
return ctl;
}
之前所有涉及到ctl指针的部分也都要一并修改。
接下来就可以制作窗口了。其实制作窗口也是准备一个图层,并在上面绘制图案,这里作者写了一个函数make_window8
void make_window8(unsigned char *buf, int xsize, int ysize, char *title)
{
static char closebtn[14][16] = {
"OOOOOOOOOOOOOOO@",
"OQQQQQQQQQQQQQ$@",
"OQQQQQQQQQQQQQ$@",
"OQQQ@@QQQQ@@QQ$@",
"OQQQQ@@QQ@@QQQ$@",
"OQQQQQ@@@@QQQQ$@",
"OQQQQQQ@@QQQQQ$@",
"OQQQQQ@@@@QQQQ$@",
"OQQQQ@@QQ@@QQQ$@",
"OQQQ@@QQQQ@@QQ$@",
"OQQQQQQQQQQQQQ$@",
"OQQQQQQQQQQQQQ$@",
"O$$$$$$$$$$$$$$@",
"@@@@@@@@@@@@@@@@"
};
int x, y;
char c;
boxfill8(buf, xsize, COL8_C6C6C6, 0, 0, xsize - 1, 0 );
boxfill8(buf, xsize, COL8_FFFFFF, 1, 1, xsize - 2, 1 );
boxfill8(buf, xsize, COL8_C6C6C6, 0, 0, 0, ysize - 1);
boxfill8(buf, xsize, COL8_FFFFFF, 1, 1, 1, ysize - 2);
boxfill8(buf, xsize, COL8_848484, xsize - 2, 1, xsize - 2, ysize - 2);
boxfill8(buf, xsize, COL8_000000, xsize - 1, 0, xsize - 1, ysize - 1);
boxfill8(buf, xsize, COL8_C6C6C6, 2, 2, xsize - 3, ysize - 3);
boxfill8(buf, xsize, COL8_000084, 3, 3, xsize - 4, 20 );
boxfill8(buf, xsize, COL8_848484, 1, ysize - 2, xsize - 2, ysize - 2);
boxfill8(buf, xsize, COL8_000000, 0, ysize - 1, xsize - 1, ysize - 1);
putfonts8_asc(buf, xsize, 24, 4, COL8_FFFFFF, title);
for (y = 0; y < 14; y++) {
for (x = 0; x < 16; x++) {
c = closebtn[y][x];
if (c == '@') {
c = COL8_000000;
} else if (c == '$') {
c = COL8_848484;
} else if (c == 'Q') {
c = COL8_C6C6C6;
} else {
c = COL8_FFFFFF;
}
buf[(5 + y) * xsize + (xsize - 21 + x)] = c;
}
}
return;
}
虽然程序代码看起来不少,其实与之前描绘鼠标的原理一样,只是在适当的位置描绘出图案。这样在主函数中也需要增加一些内容:
/* 新增了窗口的图层管理指针与buf*/
struct SHEET *sht_back, *sht_mouse, *sht_win;
unsigned char *buf_back, buf_mouse[256], *buf_win;
/* 为窗口分配图层和内存*/
sht_win = sheet_alloc(shtctl);
buf_win = (unsigned char *) memman_alloc_4k(memman, 160 * 68);
sheet_setbuf(sht_win, buf_win, 160, 68, -1);
init_screen8(buf_back, binfo->scrnx, binfo->scrny);
init_mouse_cursor8(buf_mouse, 99);
make_window8(buf_win, 160, 68, "window");
putfonts8_asc(buf_win, 160, 24, 28, COL8_000000, "Welcome to");
putfonts8_asc(buf_win, 160, 24, 44, COL8_000000, " Haribote-OS!");
sheet_slide(sht_win, 80, 72);
sheet_updown(sht_back, 0);
sheet_updown(sht_win, 1);
sheet_updown(sht_mouse, 2);
从程序中可以看出,将窗口图层的高度设置为1,处于背景图层与鼠标图层之间,这样鼠标就可以位于窗口图层之上。运行之后的显示结果如下
窗口的显示完成了,下面更进一步,由CPU进行计数,并将数字在窗口中显示出来:
void HariMain(void)
{
struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
char s[40], keybuf[32], mousebuf[128];
int mx, my, i;
unsigned int memtotal, count = 0;
struct MOUSE_DEC mdec;
struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;
struct SHTCTL *shtctl;
struct SHEET *sht_back, *sht_mouse, *sht_win;
unsigned char *buf_back, buf_mouse[256], *buf_win;
init_gdtidt();
init_pic();
io_sti();
fifo8_init(&keyfifo, 32, keybuf);
fifo8_init(&mousefifo, 128, mousebuf);
io_out8(PIC0_IMR, 0xf9);
io_out8(PIC1_IMR, 0xef);
init_keyboard();
enable_mouse(&mdec);
memtotal = memtest(0x00400000, 0xbfffffff);
memman_init(memman);
memman_free(memman, 0x00001000, 0x0009e000); /* 0x00001000 - 0x0009efff */
memman_free(memman, 0x00400000, memtotal - 0x00400000);
init_palette();
shtctl = shtctl_init(memman, binfo->vram, binfo->scrnx, binfo->scrny);
sht_back = sheet_alloc(shtctl);
sht_mouse = sheet_alloc(shtctl);
sht_win = sheet_alloc(shtctl);
buf_back = (unsigned char *) memman_alloc_4k(memman, binfo->scrnx * binfo->scrny);
buf_win = (unsigned char *) memman_alloc_4k(memman, 160 * 52);
sheet_setbuf(sht_back, buf_back, binfo->scrnx, binfo->scrny, -1);
sheet_setbuf(sht_mouse, buf_mouse, 16, 16, 99);
sheet_setbuf(sht_win, buf_win, 160, 52, -1);
init_screen8(buf_back, binfo->scrnx, binfo->scrny);
init_mouse_cursor8(buf_mouse, 99);
make_window8(buf_win, 160, 52, "counter");/* 增加计数器 */
sheet_slide(sht_back, 0, 0);
mx = (binfo->scrnx - 16) / 2;
my = (binfo->scrny - 28 - 16) / 2;
sheet_slide(sht_mouse, mx, my);
sheet_slide(sht_win, 80, 72);
sheet_updown(sht_back, 0);
sheet_updown(sht_win, 1);
sheet_updown(sht_mouse, 2);
sprintf(s, "(%3d, %3d)", mx, my);
putfonts8_asc(buf_back, binfo->scrnx, 0, 0, COL8_FFFFFF, s);
sprintf(s, "memory %dMB free : %dKB",
memtotal / (1024 * 1024), memman_total(memman) / 1024);
putfonts8_asc(buf_back, binfo->scrnx, 0, 32, COL8_FFFFFF, s);
sheet_refresh(sht_back, 0, 0, binfo->scrnx, 48);
for (;;) {
count++; /* 计数 */
sprintf(s, "%010d", count);
boxfill8(buf_win, 160, COL8_C6C6C6, 40, 28, 119, 43);
putfonts8_asc(buf_win, 160, 40, 28, COL8_000000, s);
sheet_refresh(sht_win, 40, 28, 120, 44);
io_cli();
if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) {
io_sti();
} else {
if (fifo8_status(&keyfifo) != 0) {
i = fifo8_get(&keyfifo);
io_sti();
sprintf(s, "%02X", i);
boxfill8(buf_back, binfo->scrnx, COL8_008484, 0, 16, 15, 31);
putfonts8_asc(buf_back, binfo->scrnx, 0, 16, COL8_FFFFFF, s);
sheet_refresh(sht_back, 0, 16, 16, 32);
} else if (fifo8_status(&mousefifo) != 0) {
i = fifo8_get(&mousefifo);
io_sti();
if (mouse_decode(&mdec, i) != 0) {
sprintf(s, "[lcr %4d %4d]", mdec.x, mdec.y);
if ((mdec.btn & 0x01) != 0) {
s[1] = 'L';
}
if ((mdec.btn & 0x02) != 0) {
s[3] = 'R';
}
if ((mdec.btn & 0x04) != 0) {
s[2] = 'C';
}
boxfill8(buf_back, binfo->scrnx, COL8_008484, 32, 16, 32 + 15 * 8 - 1, 31);
putfonts8_asc(buf_back, binfo->scrnx, 32, 16, COL8_FFFFFF, s);
sheet_refresh(sht_back, 32, 16, 32 + 15 * 8, 32);
mx += mdec.x;
my += mdec.y;
if (mx < 0) {
mx = 0;
}
if (my < 0) {
my = 0;
}
if (mx > binfo->scrnx - 1) {
mx = binfo->scrnx - 1;
}
if (my > binfo->scrny - 1) {
my = binfo->scrny - 1;
}
sprintf(s, "(%3d, %3d)", mx, my);
boxfill8(buf_back, binfo->scrnx, COL8_008484, 0, 0, 79, 15);
putfonts8_asc(buf_back, binfo->scrnx, 0, 0, COL8_FFFFFF, s);
sheet_refresh(sht_back, 0, 0, 80, 16);
sheet_slide(sht_mouse, mx, my);
}
}
}
}
}
显示的结果就是窗口中的数字在不断增加。但是数字显示的地方闪烁很严重,能看到背景色在一闪一闪。这是因为在刷新图层的时候,总是先刷新背景图层,再刷新窗口图层,因为数字的更新,刷新一直在进行,就会导致背景色一直在闪烁。
解决这一问题的方法很简单,如果只有窗口变化,其实不用刷新背景图层。因此refresh只对对象图层及其上面的图层进行刷新就可以了。实际修改时,sheet_refresh函数只刷新指定的图层及其上面的图层;而sheet_slide函数中,图层的移动有时会导致下面的图层露出,因此需要从最下面的图层开始刷新,而对于移动后的位置,也只需要刷新目标图层与上面的图层即可。
进行这些修改之后,计数器的数字显示闪烁问题解决了,但将鼠标放在计数器上面,又会发现鼠标的闪烁问题。这里需要另外的解决方法了。
struct SHTCTL {
unsigned char *vram, *map;
int xsize, ysize, top;
struct SHEET *sheets[MAX_SHEETS];
struct SHEET sheets0[MAX_SHEETS];
};
struct SHTCTL *shtctl_init(struct MEMMAN *memman, unsigned char *vram, int xsize, int ysize)
{
struct SHTCTL *ctl;
int i;
ctl = (struct SHTCTL *) memman_alloc_4k(memman, sizeof (struct SHTCTL));
if (ctl == 0) {
goto err;
}
ctl->map = (unsigned char *) memman_alloc_4k(memman, xsize * ysize);
if (ctl->map == 0) {
memman_free_4k(memman, (int) ctl, sizeof (struct SHTCTL));
goto err;
}
ctl->vram = vram;
ctl->xsize = xsize;
ctl->ysize = ysize;
ctl->top = -1;
for (i = 0; i < MAX_SHEETS; i++) {
ctl->sheets0[i].flags = 0;
ctl->sheets0[i].ctl = ctl;
}
err:
return ctl;
}
这里开辟了一块与VRAM同样大小的内存,命名为map,用来表示画面上的点是哪个图层的像素,如图所示:
多个图层重叠的情况下,根据这个map进行刷新,就可以清楚地确定图层互相之间的覆盖关系了。
使用map首先要向map中写入图层的号码
void sheet_refreshmap(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, int h0)
{
int h, bx, by, vx, vy, bx0, by0, bx1, by1;
unsigned char *buf, sid, *map = ctl->map;
struct SHEET *sht;
if (vx0 < 0) { vx0 = 0; }
if (vy0 < 0) { vy0 = 0; }
if (vx1 > ctl->xsize) { vx1 = ctl->xsize; }
if (vy1 > ctl->ysize) { vy1 = ctl->ysize; }
for (h = h0; h <= ctl->top; h++) {
sht = ctl->sheets[h];
sid = sht - ctl->sheets0; /* 将进行了减法计算的地址作为图层号码使用*/
buf = sht->buf;
bx0 = vx0 - sht->vx0;
by0 = vy0 - sht->vy0;
bx1 = vx1 - sht->vx0;
by1 = vy1 - sht->vy0;
if (bx0 < 0) { bx0 = 0; }
if (by0 < 0) { by0 = 0; }
if (bx1 > sht->bxsize) { bx1 = sht->bxsize; }
if (by1 > sht->bysize) { by1 = sht->bysize; }
for (by = by0; by < by1; by++) {
vy = sht->vy0 + by;
for (bx = bx0; bx < bx1; bx++) {
vx = sht->vx0 + bx;
if (buf[by * sht->bxsize + bx] != sht->col_inv) {
map[vy * ctl->xsize + vx] = sid;
}
}
}
}
return;
}
这个函数的原理其实与refreshsub函数基本一样,只不过后者是写入色号,而这里是把图层号码sid写入到map。
同样改写了sheet_refreshsub函数,使其可以使用map:
void sheet_refreshsub(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, int h0, int h1)
{
int h, bx, by, vx, vy, bx0, by0, bx1, by1;
unsigned char *buf, *vram = ctl->vram, *map = ctl->map, sid;
struct SHEET *sht;
if (vx0 < 0) { vx0 = 0; }
if (vy0 < 0) { vy0 = 0; }
if (vx1 > ctl->xsize) { vx1 = ctl->xsize; }
if (vy1 > ctl->ysize) { vy1 = ctl->ysize; }
for (h = h0; h <= h1; h++) {
sht = ctl->sheets[h];
buf = sht->buf;
sid = sht - ctl->sheets0;
bx0 = vx0 - sht->vx0;
by0 = vy0 - sht->vy0;
bx1 = vx1 - sht->vx0;
by1 = vy1 - sht->vy0;
if (bx0 < 0) { bx0 = 0; }
if (by0 < 0) { by0 = 0; }
if (bx1 > sht->bxsize) { bx1 = sht->bxsize; }
if (by1 > sht->bysize) { by1 = sht->bysize; }
for (by = by0; by < by1; by++) {
vy = sht->vy0 + by;
for (bx = bx0; bx < bx1; bx++) {
vx = sht->vx0 + bx;
if (map[vy * ctl->xsize + vx] == sid) {
vram[vy * ctl->xsize + vx] = buf[by * sht->bxsize + bx];
}
}
}
}
return;
}
根据map中记录的图层编号,分别进行刷新。由于参照map进行刷新,不需要再从下到上刷新每一个图层,因此这个函数增加了传参h1,用于和h0一起指定刷新图层的范围。
对于sheet_slide做如下修改:
void sheet_slide(struct SHEET *sht, int vx0, int vy0)
{
struct SHTCTL *ctl = sht->ctl;
int old_vx0 = sht->vx0, old_vy0 = sht->vy0;
sht->vx0 = vx0;
sht->vy0 = vy0;
if (sht->height >= 0) {
sheet_refreshmap(ctl, old_vx0, old_vy0, old_vx0 + sht->bxsize, old_vy0 + sht->bysize, 0);
sheet_refreshmap(ctl, vx0, vy0, vx0 + sht->bxsize, vy0 + sht->bysize, sht->height);
sheet_refreshsub(ctl, old_vx0, old_vy0, old_vx0 + sht->bxsize, old_vy0 + sht->bysize, 0, sht->height - 1);
sheet_refreshsub(ctl, vx0, vy0, vx0 + sht->bxsize, vy0 + sht->bysize, sht->height, sht->height);
}
return;
}
在sheet_slide函数中,首先重写map,分别对应移动前后的图层。然后调用sheet_refrehsub函数。对于原位置,需要对上层图层移动后的下层图层重新绘制;而在新位置,只需要绘制移动过去的图层即可。
简单理解,其实就是通过map把叠加后的显示结果存储起来,最终绘制的时候只需要绘制一张画面,而不是从下向上描绘多个图层来最终实现叠加效果,这样避免了从下而上的刷新,就消除了闪烁。相比原来实现叠加的方法也更进了一步。
本篇内容其实修改增加的代码不多,主要还是在前文的基础上进行,但是方法概念需要好好理解。显示问题暂告一段落,下一篇开始定时器的相关内容,敬请期待。