Esse tutorial foi traduzido da osdever.net e criado por Brandon F. Adaptado por Mauro Joel Schutz (Mounter).
Por que usar este documento?
O propósito deste tutorial é tentar explanar como criar uma simples Interface Gráfica para Usuário (GUI em inglês) para uso em um ambiente DOS ou para ser usado em um Sistema Operacional Caseiro (feito em casa!).
Requerimentos para este tutorial
Para este tutorial sobre desenvolvimento de GUI, eu ALTAMENTE recomendo fazer a leitura de:
- 256-color Graphics Tutorials (Especificamente, leia as seções BITMAPs e DOUBLEBUFFER).
- C/C++ Programming Tutorials (Se você conhece C / C++, TENHA CERTEZA que você conheça Listas Encadeadas e Árvores Binárias).
Você também precisará:
- Um compilador C/C++ (Eu uso BorlandC v3.0, apesar do DJGPP poder definitivamente fazer isto).
- Um display adaptador e monitor compatível com VGA (Pelo menos um video RAM de 64KBytes para modo 0×13).
- Um PC baseado no i286 (Este é o mínimo que o BorlandC permite), com 2MBytes de RAM.
ETAPA S.O: Tenha certeza que você pode suportar uma GUI
Em ordem para você corretamente rodar uma GUI usando este métodos, você precisará ter incluído em seu Sistema Operacional os seguintes itens: Um gerenciador de memória de alguma espécie, e (se você estiver rodando modo protegido) um V86 handler assim você poderá chamar interrupções do modo real. Seu gerenciador de memoria PRECISA incluir um malloc() ou equivalente, realloc( ) ou equivalente, e free( ) or equivalente. Nós usamos árvores binárias para armazenar as janelas e listas de controle, e, por isso, nós precisamos alocar memória dinamicamente. Um V86 handler é necessário ser implementado em ordem para fazer chamadas de modo real para a BIOS. Estás chamadas são SOMENTE usadas para atribuir o modo gráfico. Se você quiser, você pode escapar o V86 handler se você tiver atribuído um modo equivalente de funções the mudem os registradores de video. Você pode chamar as interrupções de modo real sem o V86 handler se seu Sistema Operacional é em modo real.
ETAPA GUI: Configurar o seu ambiente GUI
Esté é o passo que você deverá começar a escrever sua GUI. Você necessitará fazer um biblioteca gráfica que use bitmaps e double buffers. Exemplo de rotinas que serão postadas aqui:
unsigned char *VGA = (unsigned char *)0xA0000000L;
unsigned char *dbl_buffer;
typedef struct tagBITMAP /* a estrutura para um bitmap. */
{
unsigned int width;
unsigned int height;
unsigned char *data;
} BITMAP;
typedef struct tagRECT
{
long x1;
long y1;
long x2;
long y2;
} RECT;
void init_dbl_buffer(void)
{
dbl_buffer = (unsigned char *) malloc (SCREEN_WIDTH * SCREEN_HEIGHT);
if (dbl_buffer == NULL)
{
printf("Memoria insuficiente para double buffer.\n");
getch();
exit(1);
}
}
void update_screen(void)
{
#ifdef VERTICAL_RETRACE
while ((inportb(0x3DA) & 0x08));
while (!(inportb(0x3DA) & 0x08));
#endif
memcpy(VGA, dbl_buffer, (unsigned int)(SCREEN_WIDTH * SCREEN_HEIGHT));
}
void setpixel (BITMAP *bmp, int x, int y, unsigned char color)
{
bmp->data[y * bmp->width + x];
}
/* Desenha um retângulo preenchido em um BITMAP. Para preencher um bitmap completamente chame como
drawrect (&bmp, 0, 0, bmp.width, bmp.height, color); */
void drawrect(BITMAP *bmp, unsigned short x, unsigned short y,
unsigned short x2, unsigned short y2,
unsigned char color)
{
unsigned short tx, ty;
for (ty = y; ty < y2; ty++)
for (tx = x; tx < x2; tx++)
setpixel (bmp, tx, ty, color);
}
void draw_bitmap_old(BITMAP *bmp, int x, int y)
{
int j;
unsigned int screen_offset = ( y << 8 ) + ( y << 6 ) + x;
unsigned int bitmap_offset = 0;
for(j = 0; j < bmp->height; j++)
{
memcpy(&dbl_buffer[screen_offset], &bmp->data[bitmap_offset], bmp->width);
bitmap_offset += bmp->width;
screen_offset += SCREEN_WIDTH;
}
}
void main()
{
unsigned char key;
do
{
key = 0;
if (kbhit()) key = getch();
/* Você precisa apagar o double buffer a cada tempo ou verá coisas ruins
(vá e tente sem isso, e você poderá ver) */
memset (dbl_buffer, 0, SCREEN_WIDTH * SCREEN_HEIGHT);
/* DESENHE TODOS OS BITMAPS E FAÇA O CÓDIGO DA GUI AQUI */
/* Desenha o double buffer */
update_screen();
} while (key != 27); /* manter funcionando até encontrar o ESC */
}
Agora, como é que este código funciona? Eu primeiramente atribui um ponteiro para a memória de video no endereço 0xA0000000. Se você estiver executando em um S.O em modo protegido (exceto para alguem que está emulando o DOS), você precisará atribuir está variabel como 0xA0000. A estrutura BITMAP e RECT deve ser bastante simples: RECT define uma área na tela ([x1,y1][x2,y2]) e BITMAP define um bitmap em memória. O meio que este trabalha é você usar a largura do bitmap para descobrir quanto está em uma única linha horizontal no bitmap. Você irá efetuar este loop apartir de 0 até alcançar o tamanho a ser desenhado. Não esqueça de atribuir o offset da tela (veja na função draw_bitmap). Para fazer a GUI ou programa (você poderá usar o seguinte código abaixo para quase QUALQUER programa gráfico) executa sem problemas, você poderá fazer algo chamado double buffering. Isto significa que você irá alocar uma área que é o tamanho da tela em memória (use malloc ou equivalente) e desenhe diretamente para ele ao invez da memória de vídeo. Quando terminar de desenhar, você escreverá o doublebuffer para o memoria de vídeo e e sua imagem será mostrada. Por favor note, que você irá precisar aguardar para o que é chamado de “Vertical Retrace”. Está é a última fase que a placa de vídeo faz antes de terminar uma atualizar a tela. Quanto este estiver concluído, então você desenhará o doublebuffer para a teala e a tela não irá cintilar. Isto é muito rápido para um computador desenhar para a memoria do sistema em vez de fazer uma chamada de E/S a todo momento que você queira escrever para a tela.
ETAPA GUI: Teoria GUI – árvores binárias
Estrutura de árvores binárias são criticas para meu método de desenvolvimento de GUI. A estrutura é assim: Uma janela parente que suporta janelas filhas. A estrutura básica da janela deverá se parecer com isso:
Ou como visto no código, cada caixa acima deverá se parecer com:
struct WINDLIST
{
RECT position;
unsigned long handle;
unsigned char *caption;
unsigned long flags;
unsigned char needs_repaint;
BITMAP wbmp;
struct WINDLIST *prev, *next;
struct WINDLIST *first_child, *last_child;
struct WINDLIST *parent;
};
O RECT position contêm as coordenadas X e Y assim como as coordenadas X2 e Y2. Assim se você chamar a função drawbox você precisará enviar x1, y1, x2, y2… Como regra de segurança, NÃO faça x2 ou y2 menor que x1 ou y1. O ponteiro parent da estrutura WNDLIST acima aponta para a estrutura da janela abaixo dele, quer dizer a janela que detém a outra, e assim por diante… assim wnd.parent irá entregar o seu parente… provavelmente o desktop, a menos que você decida implementar formularios MDI. Para obter a janela mais acima na cadeia de janelas, você irá usar parent->first_child. Esta janela será desenhada na última tela… e a janela de menor ordem será desenhada primeiro (parent->last_child). Você também pode acessar as janelas seguintes e anteriores… Será assim como você deve alternar entre as janelas para serem desenhadas: Chamando assim: “repaint_children (0);”. Está deve desenhar a janela pai (janela 0), e depois percorrer todas janelas filhos, e assim por diante. Se você alterar uma janela (como mudar a cor da barra de título), então mude a variável “needs_redraw”. Quando você chamar repaint nas janelas filhos, esta irá redesenhar seu bitmap.
static void repaint_children(unsigned long parent)
{
struct WINDLIST *wnd, *child;
if (parent >= wm_num_windows)
return;
wnd = wm_handles[parent];
if (wnd == NULL)
return;
if (wnd->needs_repaint)
{
windowborder(&wnd->wbmp, 0, 0, wnd->wbmp.width, wnd->wbmp.height);
wnd->needs_repaint = 0;
}
draw_bitmap(&wnd->wbmp, wnd->position.x1, wnd->position.y1);
for (child = wnd->first_child; child != NULL; child = child->next)
repaint_children(child->handle);
}
Agora, você vai perceber que todas janelas têm bitmaps. Você precisa preencher a estrutura bitmap para cada janela sobre a sua criação. Isto significa que wnd->wbmp.width = wnd->position.x2 – wnd->position.x1 e assim por diante, bem como alocar o campo de dados do bitmap (Este é o lugar onde todas as janelas e objetos visíveis são desenhados). Se bitmap da janela não está alocado corretamente, NÃO PERMITA ELA SER DESENHADA, já que irá travar a sua GUI e, possivelmente, a sua máquina inteira. Em vez disso, você deve fechar a GUI e informar que não há mais memória. Você pode perceber que o mesmo pode ocorrer com a variável “wm_handles”. Declarea como struct WINDLIST **wm_handles. É um array de ponteiros para janelas. Também declarar um handler como um contador “wm_num_handles” como long. Absolutamente desagradável … Veja como você pode faze-lo:
Na inicialização da GUI, você precisa inicializar a lista de janelas, wm_handles, assim como criar sua primeira janela… como a janela do desktop:
wm_handles = malloc(sizeof(struct WINDLIST *)); wm_handles[0] = &wm_system_parent_window; wm_num_windows = 1;
Na criação da janela (createwin function) você precisa aumentar a variável wm_handle para acomodar mais janelas:
struct WINDLIST *wnd; wnd = malloc(sizeof(*wnd)); wm_handles = realloc(wm_handles, sizeof(struct WINDLIST *) * (wm_num_windows + 1)); wm_handles[wm_num_windows] = wnd; memset(wnd, 0, sizeof(*wnd)); wnd->handle = wm_num_windows++; /* atribua as variáveis de janela aqui... Preecha todas */
Para mover a janela para a frente da lista, você precisa PRIMEIRO remover a janela da lista e aponta-la para a janela próxima e anterior uma na outra:
/* Remove a janela da sua lista parente */
if (wnd->prev != NULL)
wnd->prev->next = wnd->next;
if (wnd->next != NULL)
wnd->next->prev = wnd->prev;
if (wnd == wnd->parent->first_child)
wnd->parent->first_child = wnd->next;
if (wnd == wnd->parent->last_child)
wnd->parent->last_child = wnd->prev;
…e ENTÃO adiciona-la para o final da lista de filhos, pela modificação da ultima janela da cadeia assim ela apontará para “está” (“this window” – wnd). Mude wnd assim ela apontará para a janela anterior a última:
/* Adicionar a janela para o final da lista de filhos */
wnd->prev = wnd->parent->last_child;
wnd->next = NULL;
if (wnd->parent->last_child != NULL)
wnd->parent->last_child->next = wnd;
wnd->parent->last_child = wnd;
if (wnd->parent->first_child == NULL)
wnd->parent->first_child = wnd;
A última parte mais dificil da manipulação de listas da GUI é a checagem de qual janela (x,y) está apontado o mouse. Verificando a janela mais ao topo primeiro, e então a próxima mais ao topo, etc… retornando quando a primeira janela com o mouse apontado (x, y) é encontrada. Você pode perceber que este chama a si mesmo também. Ele chamará a si mesmo com as janelas filhos para que possam ser verificados também:
struct WINDLIST *inwhatwin(struct WINDLIST *parent, int x, int y){
struct WINDLIST *child, *hit;
for (child = parent->last_child; child != NULL; child = child->prev)
{
hit = inwhatwin(child, x, y);
if (hit != NULL)
return hit;
}
if (pt_inrect(parent->position, x, y))
return parent;
return NULL;
}
Usando está informação básica, você poderá ter o framework para uma simples GUI, usando simples caixas preenchidas como janelas. Tudo que você precisa fazer é criar um código de encapsulamento para obter a entrada de um mouse e um teclado para mover as janelas. Simplesmente movendo as coordenadas x1, y1, x2, y2 baseado nas coordenadas do cursor…
ETAPA GUI: ALÉM DESTA GUI
Agora com está GUI básica, você pode adicionar coisas como controles de suportes (botões, caixa de texto, labels, você tem a ideia), e o suporte de menu. Eu adicionei controles de suporte em 2-3 horas de codificação e comecei a cansar. Mudança de tamanho não é difícil, você só precisa comparar a largura da janela e a altura com a do seu bitmap, e então aumentar o bitmap de acordo, e atribuir seu “needs_repaint” para 1. Neste meio, a nova janela será aumentada e sua maquina não irá morrer por valores incorretos de cor. Para menus, eu estou tendo experiências com eles. Eles são uma real dor de cabeça (eu acho). Aqui está uma dica que encontrei: Você precisa de uma estrutura para um menu e uma estrutura para cada menu selecionado. Um exemplo é o menu “Arquivo”, mas “novo”, “salvar”, “abrir”, e “sair” são selecões. Cada seleção precisa ter a oportunidade (ter um ponteiro para menu) para saltar seu próprio menu. Isto cria algo semelhante a “iniciar->programas->acessórios” dos usuários de Windows. Caixas de texto são também uma dor de cabeça, elas precisão de uma variável de texto que pode ter tamanho dinamico, para cada KByte ou algo assim, e você precisa de um BITMAP para seu controle (visível).





