harib08a:
鼠標的顯示問題:我們可以看到,鼠標移到窗口最右側之后就不能再移動了,而WIN中,鼠標是可以移動到最右邊隱藏起來的。怎么辦?把鼠標指針顯示的范圍擴寬就行!我們來修改一下HariMain來解決這個問題
//HariMain節選 if (mx > binfo->scrnx - 1) { mx = binfo->scrnx - 1; }//拓寬到右邊界左邊的一個像素 if (my > binfo->scrny - 1) { my = binfo->scrny - 1; }//拓寬到下邊界上方的一個像素
harib08b:
我們發現圖層到了畫面外,就會出現問題。這里我們一起來吧sheet_refreshsub()函數進行改進,讓它不刷新畫面以外的內容。
方 法:在進行畫面刷新之前,先判斷是否已經超過了畫面以外,超過畫面外的部分不需要進行刷新操作(下面是關鍵部分的代碼)
//sheet.c中sheet_refreshsub()節選 //判斷,刷新像素的范圍是否超出了畫面 if (bx0 < 0) { bx0 = 0; } if (by0 < 0) { by0 = 0; } if (bx1 > sht->bxsize) { bx1 = sht->bxsize; } if (by1 > sht->bysize) { by1 = sht->bysize; }
harib08c:
我們發現在圖層移動sheet_undown()中指定ctl太麻煩,於是在這一部分對此做了一些修改,目的就是不在sheet_undown()中指定ctl.
1、struct SHEET中加入struct SHTCTL *ctl
//在圖層的結構體定義struct SHEET中,加入圖層控制結構體指針struct SHTCTL *ctl struct SHEET { unsigned char *buf; int bxsize, bysize, vx0, vy0, col_inv, height, flags; struct SHTCTL *ctl; //圖層控制結構體指針struct SHTCTL *ctl };
2、對函數shtclt_init進行追加
//追加內容: 將ctl初始化為圖層控制結構體 for (i = 0; i < MAX_SHEETS; i++) { ctl->sheets0[i].flags = 0; /* FLAG=0表示該圖層未被使用 */ ctl->sheets0[i].ctl = ctl; /* 指針ctl指向圖層控制結構體 */ }
3、對函數sheet_undown進行修改
//修改內容:將ctl初始化為圖層控制結構體 void sheet_updown(struct SHEET *sht, int height) { struct SHTCTL *ctl = sht->ctl;/* 指針ctl指向圖層控制結構體 */ int h, old = sht->height; /* 記錄設置前圖層的高度 */ //........ }
4、修改sheet_refresh()、sheet_slide()、sheet_free()
//一、修改函數的參數 //二、如果函數中使用了圖層結構體SHEET,將結構體中指針ctl指向圖層控制結構體 void sheet_refresh(struct SHEET *sht, int bx0, int by0, int bx1, int by1) { if (sht->height >= 0) { /* 此時正在顯示,按照新圖層的信息進行刷新 */ sheet_refreshsub(sht->ctl, sht->vx0 + bx0, sht->vy0 + by0, sht->vx0 + bx1, sht->vy0 + by1);/* 這里修改了對sheet_refreshsub調用 */ } return; } void sheet_slide(struct SHEET *sht, int vx0, int vy0) { int old_vx0 = sht->vx0, old_vy0 = sht->vy0; sht->vx0 = vx0; sht->vy0 = vy0; if (sht->height >= 0) { /* 此時正在顯示,按照新圖層的信息進行刷新 */ sheet_refreshsub(sht->ctl, old_vx0, old_vy0, old_vx0 + sht->bxsize, old_vy0 + sht->bysize);/* 這里修改了對sheet_refreshsub調用 */ sheet_refreshsub(sht->ctl, vx0, vy0, vx0 + sht->bxsize, vy0 + sht->bysize);/* 這里修改了對sheet_refreshsub調用 */ } return; } void sheet_free(struct SHEET *sht) { if (sht->height >= 0) { sheet_updown(sht, -1); /* 這里修改了對sheet_updown的調用,隱藏該圖層 */ } sht->flags = 0; /* 圖層被釋放,flag置0 */ return; }
harib08d:
到這里,優化工作完成的差不多了,接下來繼續對系統進行拓展;接下來我們嘗試制作一下窗口。
原 理:准備一張圖層,在該圖層的緩沖區描繪一個窗口的圖像,接着把該緩沖區寫到VRAM中,讓界面刷新像素顯示即可。
1、窗口圖層制作函數make_windows8()
void make_window8(unsigned char *buf, int xsize, int ysize, char *title) { static char closebtn[14][16] = { //窗口關閉按鈕X的的數組 "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;//把這個顏色的值,給VRAM緩沖區 } } return; }
2、在HariMain中進行相應的修改
//HariMain節選 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 * 68); 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, 68, -1); /* 設置圖層緩沖區,沒有透明色 */ init_screen8(buf_back, binfo->scrnx, binfo->scrny); //初始化屏幕界面 init_mouse_cursor8(buf_mouse, 99); //X按鈕的功能鍵 make_window8(buf_win, 160, 68, "window"); //制作窗口圖層buf_win putfonts8_asc(buf_win, 160, 24, 28, COL8_000000, "Welcome to");//在圖層buf_win中顯示兩串字符串 putfonts8_asc(buf_win, 160, 24, 44, COL8_000000, " Haribote-OS!"); 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); //設置窗口圖層sht_win的上下滑動 sheet_updown(sht_back, 0); //設定背景圖層的高度為0(在最底層) sheet_updown(sht_win, 1); //設置窗口圖層的高度為1(在中間層) sheet_updown(sht_mouse, 2); //設置鼠標圖層的高度為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); //刷新屏幕顯示
harib08e:
小實驗:我們在上面一共有了3個圖層:背景、窗口、鼠標。讓我們抽一下風把這三個圖層的高度改一下,看看會怎么樣?我們把鼠標圖層放在窗口下面,背景圖層在最底下(上圖運行成功,說明程序沒問題)
//修改HariMain中的圖層高度設置 sheet_updown(sht_back, 0);//背景最底層 sheet_updown(sht_mouse, 1);//鼠標中間層 sheet_updown(sht_win, 2);//窗口最高層
harib08f:
我們已經能順利的制作出窗口了,現在我們來做個好玩的東西---計數器。在上一步,我們已經成功的繪制出了窗口,接下來只需要對窗口圖層的內容做一些修改(這部分比較簡單,直接上代碼)
//HariMain節選 make_window8(buf_win, 160, 52, "counter"); //窗口標題: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);//刷新顯示界面
harib08g:
我們make run一下,發現窗口中的內容在不斷的閃爍。到這里位置,我們每次刷新顯示都是從最低圖層開始,一直向上把所有的圖層都王VRAM中寫一遍。但是很多圖層的內容是沒有變化的(比如背景圖層);在這里我們做以下消除閃爍的優化處理:僅對refresh對象及以上的圖層進行刷新就行了。
1、修改完成刷新工作的sheet_refreshsub()函數:追加一個參數h0表示需要刷新的最低圖層。這樣在循環處加一個判斷條件:for (h = h0; h <= ctl->top; h++)只對層數比h0高的圖層進行刷新;
2、修改相關的函數:上面我們對sheet_refreshsub()函數追加了h0參數,接下來我們把所有調用了sheet_refreshsub()的函數都修改一下。
sheet_refreshsub(sht->ctl, sht->vx0 + bx0, sht->vy0 + by0, sht->vx0 + bx1, sht->vy0 + by1, sht->height);
harib08h:
上面我們已經消除了窗口刷新時的閃爍,接下來我們來消除鼠標的閃爍!
1、map(地圖)的定義和初始化:開辟一塊和VRAM大小一樣的內存--map;這塊內存用來表示畫面上的點是那個圖層的像素,所以相當於是圖層的地圖。我們要理解作者的用意:map和VRAM設計成大小一樣的,就是要把map當做一個中間的緩沖區域(相當於VRAM的一個緩存),這樣可以對VRAM的操作進行加速。
//bootpack.h和sheet.c節選 //定 義:map定義在圖層管理結構體中 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) { //...函數節選: ................ //開辟一個4KB的內存空間,map指向這個空間起始地址 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; /* 圖層標識FLAG=0表示該圖層沒有被使用 */ ctl->sheets0[i].ctl = ctl; /* 初始化圖層ctl為圖層管理結構體的地址 */ } err: return ctl; }
2、在map中寫入圖層號碼:我們用當前圖層的地址(sht)減去第一塊圖層的地址(ctl->sheets0)的偏移量來表示該圖層的號碼sid(sheet ID)
圖片P215的圖片來一個;
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]; //前圖層的地址(sht)減去第一塊圖層的地址(ctl->sheets0)的偏移量 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) { //在對需要刷新的部分進行刷新時,不再是寫到VRAM中,而是將該像素的sid寫到map中 map[vy * ctl->xsize + vx] = sid; } } } } return; }
3、對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; /* refresh刷新的范圍超出了畫面 */ //....................... for (h = h0; h <= h1; h++) { //只對h0到h1的圖層進行跟新操作 sht = ctl->sheets[h]; //當前圖層的地址 buf = sht->buf; //當前圖層的緩沖區內容 sid = sht - ctl->sheets0;//計算當前圖層sid的值 /* 倒推出刷新的范圍(不懂看前一天的內容) */ 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; //只有當前sid和map中該像素處sid相等,才將緩存寫到VRAM中 if (map[vy * ctl->xsize + vx] == sid) { vram[vy * ctl->xsize + vx] = buf[by * sht->bxsize + bx]; } } } } return; }
4、修改調用了sheet_refreshsub()的3個函數:在函數sheet_refresh()\sheet_slide()\sheet-updown中都對函數sheet_refreshsub()有調用,我們找到這三個函數的相應位置,按照sheet_refreshsub()新的參數,重寫一下調用.