《30天自制操作系統》筆記(09)——繪制窗口
進度回顧
上一篇中介紹了圖層式窗口管理的思路和算法。在此基礎上,本篇就解決繪制窗口及其簡單的優化問題。
這里稍微吐槽一下《30天自制操作系統》原作者。全書我剛剛看了三分之一,寫得確實不錯,但是我能感受到原作者是習慣用匯編語言和匯編思維來寫程序的。雖然書里盡量使用了C語言,但給我一種用C寫匯編的感覺。也可能是原作者故意簡化了OS開發過程,方便初學者理解吧。幸好這在我預料之中,一開始我就打算先看完這本書然后自己再從零設計OS的。
看似立體的窗口其實是畫出來的
從第一次見到電腦屏幕上的窗口開始,我就想知道它是怎么做到的。一直在懷疑,現在終于確信了看似立體的窗口、按鈕什么的,全是畫出來的。
1 void make_window8(unsigned char *buf, int xsize, int ysize, char *title) 2 { 3 static char closebtn[14][16] = { 4 "OOOOOOOOOOOOOOO@", 5 "OQQQQQQQQQQQQQ$@", 6 "OQQQQQQQQQQQQQ$@", 7 "OQQQ@@QQQQ@@QQ$@", 8 "OQQQQ@@QQ@@QQQ$@", 9 "OQQQQQ@@@@QQQQ$@", 10 "OQQQQQQ@@QQQQQ$@", 11 "OQQQQQ@@@@QQQQ$@", 12 "OQQQQ@@QQ@@QQQ$@", 13 "OQQQ@@QQQQ@@QQ$@", 14 "OQQQQQQQQQQQQQ$@", 15 "OQQQQQQQQQQQQQ$@", 16 "O$$$$$$$$$$$$$$@", 17 "@@@@@@@@@@@@@@@@" 18 }; 19 int x, y; 20 char c; 21 boxfill8(buf, xsize, COL8_C6C6C6, 0, 0, xsize - 1, 0 ); 22 boxfill8(buf, xsize, COL8_FFFFFF, 1, 1, xsize - 2, 1 ); 23 boxfill8(buf, xsize, COL8_C6C6C6, 0, 0, 0, ysize - 1); 24 boxfill8(buf, xsize, COL8_FFFFFF, 1, 1, 1, ysize - 2); 25 boxfill8(buf, xsize, COL8_848484, xsize - 2, 1, xsize - 2, ysize - 2); 26 boxfill8(buf, xsize, COL8_000000, xsize - 1, 0, xsize - 1, ysize - 1); 27 boxfill8(buf, xsize, COL8_C6C6C6, 2, 2, xsize - 3, ysize - 3); 28 boxfill8(buf, xsize, COL8_000084, 3, 3, xsize - 4, 20 ); 29 boxfill8(buf, xsize, COL8_848484, 1, ysize - 2, xsize - 2, ysize - 2); 30 boxfill8(buf, xsize, COL8_000000, 0, ysize - 1, xsize - 1, ysize - 1); 31 putfonts8_asc(buf, xsize, 24, 4, COL8_FFFFFF, title); 32 for (y = 0; y < 14; y++) { 33 for (x = 0; x < 16; x++) { 34 c = closebtn[y][x]; 35 if (c == '@') { 36 c = COL8_000000; 37 } else if (c == '$') { 38 c = COL8_848484; 39 } else if (c == 'Q') { 40 c = COL8_C6C6C6; 41 } else { 42 c = COL8_FFFFFF; 43 } 44 buf[(5 + y) * xsize + (xsize - 21 + x)] = c; 45 } 46 } 47 return; 48 }
這個繪制"關閉"按鈕的代碼和繪制鼠標的代碼多么相似!其實都是畫出來的立體效果而已。
當然,這么靜態地在主函數里繪制一次還不夠,還要給窗口添加一個圖層,并注冊到OS的sheetctrl鏈表中。這樣,當窗口發生激活、移動時,sheetctrl就會刷新這個窗口了。
優化1-減少需要刷新的圖層
把"每次刷新都遍歷所有圖層"改進為"僅對內容發生變化的圖層及其以上的圖層進行刷新"。
1 void sheet_refreshsub(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, int h0) 2 { 3 /* 略 */ 4 for (h = h0; h <= ctl->top; h++) { 5 /* 略 */ 6 } 7 return; 8 }
調用sheet_refreshsub()函數的位置都要相應修改。

1 void sheet_refresh(struct SHEET *sht, int bx0, int by0, int bx1, int by1) 2 { 3 if (sht->height >= 0) { /* 如果正在顯示,則按新圖層的信息進行刷新 */ 4 sheet_refreshsub(sht->ctl, sht->vx0 + bx0, sht->vy0 + by0, sht->vx0 + bx1, sht->vy0 + by1, sht->height); 5 } 6 return; 7 } 8 9 void sheet_slide(struct SHEET *sht, int vx0, int vy0) 10 { 11 int old_vx0 = sht->vx0, old_vy0 = sht->vy0; 12 sht->vx0 = vx0; 13 sht->vy0 = vy0; 14 if (sht->height >= 0) { /* 如果正在顯示,則按新圖層的信息進行刷新 */ 15 sheet_refreshsub(sht->ctl, old_vx0, old_vy0, old_vx0 + sht->bxsize, old_vy0 + sht->bysize, 0); 16 sheet_refreshsub(sht->ctl, vx0, vy0, vx0 + sht->bxsize, vy0 + sht->bysize, sht->height); 17 } 18 return; 19 } 20 21 void sheet_updown(struct SHEET *sht, int height) 22 { 23 /* 略 */ 24 25 /* 以下主要是對sheets[]的重新排列 */ 26 if (old > height) { /* 比以前低 */ 27 if (height >= 0) { 28 /* 中間的提起 */ 29 for (h = old; h > height; h--) { 30 ctl->sheets[h] = ctl->sheets[h - 1]; 31 ctl->sheets[h]->height = h; 32 } 33 ctl->sheets[height] = sht; 34 sheet_refreshsub(ctl, sht->vx0, sht->vy0, sht->vx0 + sht->bxsize, sht->vy0 + sht->bysize, height + 1); 35 } else { /* 隱藏 */ 36 if (ctl->top > old) { 37 /* 把上面的降下來 */ 38 for (h = old; h < ctl->top; h++) { 39 ctl->sheets[h] = ctl->sheets[h + 1]; 40 ctl->sheets[h]->height = h; 41 } 42 } 43 ctl->top--; /* 正在顯示的圖層減少了一個,故最上面的高度也減少 */ 44 sheet_refreshsub(ctl, sht->vx0, sht->vy0, sht->vx0 + sht->bxsize, sht->vy0 + sht->bysize, 0); 45 } 46 } else if (old < height) { /* 比以前高 */ 47 if (old >= 0) { 48 /* 中間的圖層往下降一層 */ 49 for (h = old; h < height; h++) { 50 ctl->sheets[h] = ctl->sheets[h + 1]; 51 ctl->sheets[h]->height = h; 52 } 53 ctl->sheets[height] = sht; 54 } else { /*從隱藏狀態變為顯示狀態 */ 55 /* 把在上面的圖層往上提高一層 */ 56 for (h = ctl->top; h >= height; h--) { 57 ctl->sheets[h + 1] = ctl->sheets[h]; 58 ctl->sheets[h + 1]->height = h + 1; 59 } 60 ctl->sheets[height] = sht; 61 ctl->top++; /* 顯示中的圖層增加了一個,故最上面的高度也增加 */ 62 } 63 sheet_refreshsub(ctl, sht->vx0, sht->vy0, sht->vx0 + sht->bxsize, sht->vy0 + sht->bysize, height); 64 } 65 return; 66 }
在sheet_slide函數里,圖層的移動有時會導致下面的圖層露出,所以要從最下面開始刷新。另一方面,在移動目標處,比新移來的圖層位置還要低的圖層沒有什么變化,而且只是隱藏起來了,所以只要刷新移動的圖層和它上面的圖層就可以了。
優化2-借助圖層map
這是個空間換時間的優化方式。
首先,開辟一塊內存,大小和VRAM一樣,我們稱之為map。這個map用來表示畫面上的點是哪個圖層的像素,所以它相當于圖層的地圖。
文字不好表達清楚,看圖則一目了然。
當刷新圖層1的時候,如果對照這個map刷新,就不會覆蓋被其他圖層擋住的部分了。
原理就是這樣,并不高深。代碼也很簡單,而且不必要看了。
效果如下。
總結
本篇終于能夠畫出類似windows應用程序窗口那樣的窗口。原來那么立體的窗口真是一點一點畫出來的。解決了10多年的疑問,真舒服。
原作者對繪制窗口這個問題進行了多次優化,效果很好。不知道windows的窗口繪制是如何優化的,不知道《30天自制操作系統》后續章節還有沒有繼續優化?革命尚未成功,同志仍需努力啊。
請查看下一篇《《30天自制操作系統》筆記(10)——定時器》
文章列表