-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathweaver-window.cweb
4622 lines (4008 loc) · 168 KB
/
weaver-window.cweb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\font\sixteen=cmbx16
\font\twelve=cmr12
\font\fonteautor=cmbx12
\font\fonteemail=cmtt10
\font\twelvenegit=cmbxti12
\font\twelvebold=cmbx12
\font\trezebold=cmbx13
\font\twelveit=cmsl12
\font\monodoze=cmtt12
\font\it=cmti12
\voffset=0,959994cm % 3,5cm de margem superior e 2,5cm inferior
\parskip=6pt
\def\titulo#1{{\noindent\sixteen\hbox to\hsize{\hfill#1\hfill}}}
\def\autor#1{{\noindent\fonteautor\hbox to\hsize{\hfill#1\hfill}}}
\def\email#1{{\noindent\fonteemail\hbox to\hsize{\hfill#1\hfill}}}
\def\negrito#1{{\twelvebold#1}}
\def\italico#1{{\twelveit#1}}
\def\monoespaco#1{{\monodoze#1}}
\long\def\abstract#1{\parshape 10 0.8cm 13.4cm 0.8cm 13.4cm
0.8cm 13.4cm 0.8cm 13.4cm 0.8cm 13.4cm 0.8cm 13.4cm 0.8cm 13.4cm
0.8cm 13.4cm 0.8cm 13.4cm 0.8cm 13.4cm
\noindent{{\twelvenegit Abstract: }\twelveit #1}}
\def\resumo#1{\parshape 10 0.8cm 13.4cm 0.8cm 13.4cm
0.8cm 13.4cm 0.8cm 13.4cm 0.8cm 13.4cm 0.8cm 13.4cm 0.8cm 13.4cm
0.8cm 13.4cm 0.8cm 13.4cm 0.8cm 13.4cm
\noindent{{\twelvenegit Resumo: }\twelveit #1}}
\def\secao#1{\vskip12pt\noindent{\trezebold#1}\parshape 1 0cm 15cm}
\def\subsecao#1{\vskip12pt\noindent{\twelvebold#1}}
\def\subsubsecao#1{\vskip12pt\noindent{\negrito{#1}}}
\def\referencia#1{\vskip6pt\parshape 5 0cm 15cm 0.5cm 14.5cm 0.5cm 14.5cm
0.5cm 14.5cm 0.5cm 14.5cm {\twelve\noindent#1}}
%@* .
\twelve
\vskip12pt
\titulo{Interface de Janela Weaver}
\vskip12pt
\autor{Thiago Leucz Astrizi}
\vskip6pt
\email{thiago@@bitbitbit.com.br}
\vskip6pt
\abstract{This article contains the implementation of a portable
Windowing API, which can be used to create a single window in
Windows, Linux, BSD, or a canvas in Web Assembly running in a web
browser. You can set a fullscreen mode, change to windowed mode,
resize it, use OpenGL commands and get input from mouse and
keyboard. All this can be achieved by the portable API defined in
this work.}
\vskip 0.5cm plus 3pt minus 3pt
\resumo{Este artigo contém a implementação de uma interface de uso de
janelas. Ela pode ser usada para criar uma única janela no Windows,
Linux, BSD, ou então um ``canvas'' rodando Web Assembly em um
navegador de Internet. Você pode ativar um modo de tela cheia, mudar
para um modo em janela, mudar o tamanho da janela, usar comandos
OpenGL e obter entrada do mouse e teclado. Tudo isso pode ser obtido
pela interface de aplicação portável definida neste trabalho.}
\secao{Índice}
\def\indice#1{\leaders\hbox{.}\hfill #1}
1. Introdução \indice{02}
1.1. Programação Literária e Notação \indice{03}
1.2. Funções de API a serem Definidas \indice{04}
2. Criando e Gerenciando a Janela \indice{05}
2.1. Obtendo a Resolução da Tela \indice{05}
2.1.1. Obtendo a Resolução da Tela no X \indice{06}
2.1.2. Obtendo a Resolução da Tela no Web Assembly \indice{07}
2.1.3. Obtendo a Resolução da Tela no Windows \indice{07}
2.2. Criando uma Janela \indice{07}
2.2.1. Criando uma Janela no X \indice{07}
2.2.2. Criando uma Janela com Web Assembly \indice{11}
2.2.3. Criando uma Janela no Windows \indice{13}
2.2.4. Definindo a Função de Criação de Janelas para Todo Ambiente \indice{16}
2.3. Configurando o OpenGL \indice{16}
2.3.1. Configurando OpenGL ES no X11 \indice{16}
2.3.2.Configurando OpenGL no Web Assembly \indice{19}
2.3.3. Configurando OpenGL no Windows \indice{19}
2.3.4. Escolhendo a Versão do OpenGL \indice{43}
2.4. Fechando uma Janela \indice{43}
2.4.1. Fechando uma Janele no X11 \indice{44}
2.4.2. Fechando uma Janela em Web Assembly \indice{44}
2.4.3. Fechando uma Janela no Windows \indice{44}
2.5. Renderizando a Janela \indice{45}
2.5.1. Renderizando a Janela no X \indice{45}
2.5.2. Renderizando a Janela no Emscripten \indice{45}
2.5.3. Renderizando a Janela no Windows \indice{45}
2.6. Obtendo o Tamanho da Janela \indice{46}
2.6.1. Obtendo o Tamanho da Janela no X \indice{46}
2.6.2. Obtendo o Tamanho da Janela no Web Assembly \indice{46}
2.6.3. Obtendo o Tamanho da Janela no Windows \indice{47}
2.7. Checando o modo de tela cheia \indice{47}
2.7.1. Checando modo de tela cheia no X11 \indice{47}
2.7.2. Checando modo de tela cheia no Web Assembly \indice{48}
2.7.3. Checando modo de tela cheia no Windows \indice{48}
2.8. Alternando entre Tela Cheia e Janela \indice{48}
2.8.1. Alternando entre Tela Cheia e Janela no X11 \indice{49}
2.8.2. Alternando entre Tela Cheia e Janela no Web Assembly \indice{50}
2.8.3. Alternando entre Tela Cheia e Janela no Windows \indice{52}
2.9. Redimencionando a Janela \indice{52}
2.9.1. Redimencionando a Janela no X11 \indice{52}
2.9.2. Redimencionando a Janela no Web Assembly \indice{53}
2.9.3. Redimencionando a Janela no Windows \indice{54}
3. Gerenciando Entrada \indice{54}
3.1. Definindo o Teclado e Mouse \indice{54}
3.2. Lendo o Teclado \indice{56}
3.2.1. Lendo o Teclado no X \indice{56}
3.2.2. Lendo o Teclado no Web Assembly \indice{59}
3.2.3. Lendo o Teclado no Windows \indice{61}
3.2.4. Código Adicional para Suporte de Teclado \indice{63}
3.3. Lendo o Mouse \indice{65}
3.3.1. Lendo o Mouse no X \indice{66}
3.3.2. Lendo o Mouse no Web Assembly \indice{67}
3.3.3. Lendo o Mouse no Windows \indice{68}
3.3.4. Código Adicional para Suporte de Mouse \indice{70}
3.4. Funções Adicionais de Entrada \indice{72}
4. Estrutura Final do Arquivo \indice{72}
\secao{1. Introdução}
Um programa de computador gráfico precisa de um espaço no qual ele
pode desenhar na tela. Em alguns ambientes, como videogames, por
exemplo, cada programa em execução simplesmente tem controle de toda a
tela automaticamente sem que seja necessário reservá-lo ou pedi-lo
para um Sistema Operacional. Por outro lado, quando um programa
executa em um computador com algum ambiente gráfico moderno, é
necessário pedir para que uma região chamada ``janela'' seja
criada. Nela o programa passa a ter controle sobre o seu conteúdo e
pode desenhar na região.
Além de criar uma janela, é importante que tenhamos a capacidade de
entrar e sair do modo tela-cheia se estivermos em ambiente que permite
isso. E se estivermos em modo de janela, mudar o tamanho da
janela.
\subsecao{1.1. Programação Literária e Notação}
Este artigo utiliza a técnica de ``Programação Literária'' para
desenvolver a API de gerador de números aleatórios. Esta técnica foi
apresentada em [Knuth, 1984] e tem por objetivo desenvolver
\italico{softwares} de tal forma que um programa de computador a ser compilado
é exatamente igual a um documento escrito para pessoas detalhando e
explicando o código. O presente documento não é algo independente do
código, mas sim consiste no próprio código-fonte do projeto.
Ferramentas automáticas são utilizadas para extrair o código deste
documento, colocá-lo na ordem correta e produzir o código que é
passado para o compilador.
Por exemplo, neste artigo serão definidos dois arquivos
diferentes: \monoespaco{window.c} e \monoespaco{window.h}, os quais
podem ser inseridos estaticamente em qualquer projeto, ou compilados
como uma biblioteca compartilhada. O conteúdo de \monoespaco{window.h}
é:
@(src/window.h@>=
#ifndef WEAVER_WINDOW
#define WEAVER_WINDOW
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h> // Define tipo 'bool'
#if !defined(_WIN32)
#include <sys/param.h> // Necessário no BSD, mas causa problema no Windows
#endif
@<Cabeçalho OpenGL@>
@<Define Macros@>
@<Estruturas de Dados de Janela@>
@<Declarações de Janela@>
#ifdef __cplusplus
}
#endif
#endif
@
As duas primeiras linhas assim como a última são macros que impedem
que garantem que as funções e variáveis declaradas ali serão inseridas
no máximo uma só vez em cada unidade de compilação. Também colocamos
macros para checar se estamos compilando o código como C ou C++. Se
estivermos em C++, avisamos o compilador que estamos definindo tudo
como código C e garantimos que não vamos modificar nada usando
sobrecarga de operadores. O código poderá assim ser armazenado de
maneira mais compacta.
As partes vermelhas no código acima mostram que código será inserido
ali no futuro.
Cada trecho de código tem um título, que no caso acima
é \monoespaco{src/window.h}. O título indica onde o código será
inserido. No caso acima, o código irá para um arquivo. Em trechos de
código futuros, haverá diversos títulos, inclusive um com exatamente o
nome das partes em vermelho do código acima. Se o título de um trecho
de código é igual um trecho em vermelho a ser inserido, é ali que tal
trecho de código deve ser posicionado no processo de compilação.
Como um segundo exemplo de código, também declararemos aqui que quando
estamos em modo de depuração (ou seja, quando a
macro \monoespaco{W\_DEBUG\_WINDOW} está definida) iremos precisar das
declarações de funções de entrada e saída padrão, já que nosso código
se tornará mais verboso:
@<Cabeçalhos@>=
#if defined(W_DEBUG_WINDOW)
#include <stdio.h>
#endif
@
Note que ainda não informamos onde exatamente o código denominado
``Cabeçalho'' será colocado. Faremos isso posteriormente.
\subsecao{1.2. Funções de API a serem Definidas}
Neste artigo iremos definir as seguintes funções:
@<Declarações de Janela@>=
bool _Wcreate_window(struct _Wkeyboard *keyboard, struct _Wmouse *mouse);
@
Esta é a função que irá criar uma nova janela. Por padrão, uma janela
em tela cheia. Se a macro \monoespaco{W\_WINDOW\_NO\_FULLSCREEN}
estiver definida, ao invés disso, ele criará uma janela que não está
em modo de tela-cheia (se suportado pelo sistema).
Se a macro \monoespaco{W\_DEBUG\_WINDOW} estiver definida, esta função
também imprimirá na tela informação sobre o ambiente gráfico. Sua
resolução, por exemplo, ou possivelmente outras informações que possam
ser relevantes.
A função irá inicializar as estruturas de dado do mouse e teclado.
Em caso de erro, a função retornará falso.
@<Declarações de Janela@>=
bool _Wdestroy_window(void);
@
Esta função vai fechar a janela aberta, liberando qualquer recurso que
tenha sido alocado pela função anterior ao criar janela. A função deve
ser invocada sempre após a janela já ter sido criada. Em caso de erro,
retorna falso.
@<Declarações de Janela@>=
bool _Wrender_window(void);
@
Esta função irá efetivamente renderizar na tela todos os comandos
OpenGL pendentes desde a última renderização. Retornará falso em caso
de erro. Esta função provavelmente será chamada no fim de cada
iteração de um laço principal.
@<Declarações de Janela@>=
bool _Wget_screen_resolution(int *resolution_x, int *resolution_y);
@
Esta função obtém a resolução da tela e a armazena nos ponteiros
passados. Se houver mais de uma, retornará aquela que o sistema
identifica como sendo a principal. Se um erro ocorrer, ela retornará
falso e a resolução será armazenada como zero.
@<Declarações de Janela@>=
bool _Wget_window_size(int *width, int *height);
@
Esta função obtém o tamanho em pixels da janela atual e armazena a
informação nos ponteiros passados como argumento. Se nós não temos
nenhuma janela, ou em caso de erro, a função retorna falso e armazena
zero nos ponteiros. Já em caso de sucesso, ela retorna verdadeiro e
armazena o resultado correto nos ponteiros.
@<Declarações de Janela@>=
void _Wget_window_input(unsigned long long current_time,
struct _Wkeyboard *keyboard,
struct _Wmouse *mouse);
@
Esta função periodicamente deve ser chamada para atualizar o estado do
teclado e mouse. O primeiro argumento é o tempo atual medido em alguma
unidade de tempo. Os próximos argumentos são estruturas que
representam teclado e mouse a serem atualizadas.
@<Declarações de Janela@>+=
void _Wflush_window_input(struct _Wkeyboard *keyboard,
struct _Wmouse *mouse);
@
Esta função limpa o estado de nosso teclado e mouse. Ela deve ser
chamada toda vez que pararmos de ler periodicamente o mouse e teclado
com chamadas a \monoespaco{\_Wget\_window\_input}. O resultado de
chamar esta função é que reiniciamos o estado de nosso mouse e
teclado, parando de considerar qualquer tecla como pressionada ou
solta.
@<Declarações de Janela@>+=
bool _Wis_fullscreen(void);
@
Esta função retorna se estamos em modo de tela-cheia ou não. O
resultado é indefinido se uma janela não foi criada.
@<Declarações de Janela@>+=
void _Wtoggle_fullscreen(void);
@
Esta função alterna entre o modo de tela cheia e modo em
janela. Dependendo do ambiente ela pode falhar.
@<Declarações de Janela@>+=
bool _Wresize_window(int width, int height);
@
Esta função muda o tamanho da janela se ela existir e se não
estivermos no modo de tela-cheia. Caso a operação falhe devido a estas
condições não estiverem satisfeitas, a função retorna falso.
@<Declarações de Janela@>+=
void _Wset_resize_function(void (*func)(int, int, int, int));
@
Esta função registra uma função para que seja executada toda vez que
ocorrer uma mudança no tamanho de nossa janela. A função recebe como
argumentos a largura e altura anterior da janela e como dois últimos
argumentos a largura e altura da janela após o redimencionamento.
\secao{2. Criando e Gerenciando a Janela}
Nesta seção definiremos funções e códigos que envolvem criar uma
janela e criar um contexto OpenGL compatível com OpenGL ES 2.0 nelas.
\subsecao{2.1. Obtendo a Resolução da Tela}
Antes de criar nossa janela, é importante checar qual a resolução da
tela e da janela. Se estivermos iniciando em modo de tela cheia, este
será o tamanho da nossa janela. Isso é feito de maneira diferente
dependendo do sistema operacional.
Vamos também criar uma variável estática que funcionará como um cache
que memorizará o último tamanho da janela em pixels que
obtemos. Memorizar o tamanho da janela é importante porque precisamos
deste valor para depois transformar as coordenadas da posição do mouse
que iremos medir. Praticamente todos os ambientes informam as
coordenadas na janela usando como origem o canto superior
esquerdo. Contudo, a API Weaver prefere usar como origem o canto
inferior esquerdo para estar mais de acordo com a convenção
matemática. Para fazer a transformação de coordenada, é importante
memorizar o tamanho da janela para não termos que medi-la toda hora:
@<Variáveis Locais@>=
static int window_size_y = 0, window_size_x = 0;
@
\subsubsecao{2.1.1. Obtendo a Resolução da Tela no X}
O Sistema de Janelas X, também conhecido como X11, é um sistema de
janelas presente em muitos Sistemas Operacionais, como Linux, BSD, e
até mesmo no MacOX X, onde ele está presente para garantir
compatibilidade com programas mais antigos desenvolvidos antes de seu
sistema de janelas atual. Iremos começar com a implementação de nossa
função no X11 por ser o sistema de janelas mais amplamente presente.
O X11 funciona em uma arquitetura de cliente-servidor. Quando criamos
um programa gráfico, criamos um cliente que se comunica com o servidor
X usando \italico{sockets}. Todas as operações como a criação de
janelas, redimencionar a janela e mais, são feitas com o cliente
pedindo para que elas sejam feitas para o servidor, que executa os
pedidos se possível.
Iremos usar o X11 sempre que não estivermos usando o Windows (que não
o fornece) e nem estivermos compilando Web Assembly (navegadores de
Internet também não o implementam). Antes de usar o X11, precisamos
inserir seu cabeçalho relevante:
@<Cabeçalhos@>=
#if defined(__linux__) || defined(BSD)
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#endif
@
No X11, como temos que nos comunicar com um servidor, assumimos que
temos uma variável com algum tipo de estrutura de dados com informação
sobre a nossa conexão. Esta variável é chamada de
``\monoespaco{display}''.
Usando tal conexão, que assumimos ser uma variável estática
em \monoespaco{window.c}, ler a resolução da tela é feito com a função
abaixo:
@<Funções da API@>=
#if defined(__linux__) || defined(BSD)
bool _Wget_screen_resolution(int *resolution_x, int *resolution_y){
bool keep_alive = true; //Devemos manter a conexão ativa?
// A primeira coisa a ser feita é chamar esta função para preparar o X11
// para código multi-thread:
XInitThreads();
// Se não temos uma conexcão ativa, criamos ela:
if(display == NULL){
display = XOpenDisplay(NULL);
keep_alive = false;
}
// Obtemos a tela padrão:
int screen = DefaultScreen(display);
// E perguntamos a resolução
*resolution_x = DisplayWidth(display, screen);
*resolution_y = DisplayHeight(display, screen);
// Se não havia uma conexão ativa ao servidor X antes de invocar a função,
// fechamos a conexão que abrimos para obter a resolução:
if(!keep_alive){
XCloseDisplay(display);
display = NULL;
}
// Se tudo deu certo podemos retornar verdadeiro:
return true;
}
#endif
@
Claro, temos que assumir a existência da seguinte variável estática:
@<Variáveis Locais@>=
#if defined(__linux__) || defined(BSD)
static Display *display = NULL; //Conexão com servidor e info sobre tela
#endif
@
\subsubsecao{2.1.2. Obtendo a Resolução da Tela no Web Assembly}
Se estivermos executando em Web Assembly, então assumimos estar em um
Navegador de Internet. A nossa ``janela'' será um ``canvas'' HTML. E
nesse caso para obter o tamanho da tela, temos que recorrer à
invocação de Javascript. Fazemos isso com ajuda da API do Emscripten:
@<Funções da API@>+=
#if defined(__EMSCRIPTEN__)
bool _Wget_screen_resolution(int *resolution_x, int *resolution_y){
*resolution_x = EM_ASM_INT({
return window.screen.width * window.devicePixelRatio;
});
*resolution_y = EM_ASM_INT({
return window.screen.height * window.devicePixelRatio;
});
return true;
}
#endif
@
\subsubsecao{2.1.3. Obtendo a Resolução da Tela no Windows}
Finalmente, no Windows, isso é feito simplesmente chamando uma função
da API que nos dá esta informação:
@<Funções da API@>+=
#if defined(_WIN32)
bool _Wget_screen_resolution(int *resolution_x, int *resolution_y){
*resolution_x = GetSystemMetrics(SM_CXSCREEN);
*resolution_y = GetSystemMetrics(SM_CYSCREEN);
return true;
}
#endif
@
\subsecao{2.2. Criando uma Janela}
Agora descreveremos como criar uma janela nos diferentes tipos de
ambiente que suportamos: ambiente gráfico X11, Windows e rodando em
navegador web com Web Assembly.
\subsubsecao{2.2.1. Criando uma Janela no X}
Os passos para criar uma janela no X11 são:
0. Avisamos a biblioteca X que pode ser que múltiplas threads tentem
se comunicar com ele. É raro haver motivos para se fazer isso, mas é
útil se preparar só para o caso de acontecer. Essa precisa ser a
primeira coisa a ser feita antes de usar outras funções da biblioteca.
1. Abrimos uma conexão com o servidor. Se isso for bem-sucedido, o
servidor nos revela várias informações relevantes sobre a tela.
2. Lemos a resolução da tela com a função que definimos na seção 2.1.
3. Enviamos uma nova mensagem para o servidor pedindo que a janela
seja criada. Em princípio criaremos uma janela com o máximo de tamanho
permitido dada a resolução da tela.
4. Memorizamos o valor inicial da altura e largura da janela.
Estes passos são implementados por meio das seguintes funções e
macros:
@<X11: Criar Janela@>=
#if defined(__linux__) || defined(BSD)
int screen_resolution_x, screen_resolution_y; // Resolução da tela
/* Passo 0: */
XInitThreads();
/* Passo 1: */
display = XOpenDisplay(NULL);
if(display == NULL){
#if defined(W_DEBUG_WINDOW)
fprintf(stderr, "ERROR: Failed to connect to X11 server.\n");
#endif
return false; // Não conseguiu se conectar
}
/* Passo 2: */
_Wget_screen_resolution(&screen_resolution_x, &screen_resolution_y);
/* Passo 3: */
window = XCreateSimpleWindow(display, // Conexão com o X11
DefaultRootWindow(display), // Janela-mãe
0, 0, // Posição da janela criada
screen_resolution_x, // Largura
screen_resolution_y, // Altura
0, 0, // Espessura e cor da borda
0); // Cor padrão da janela
/* Passo 4: */
window_size_x = screen_resolution_x;
window_size_y = screen_resolution_y;
#endif
@
Este código assume que temos as seguintes variável declarada para
armazenar e memorizar a janela criada:
@<Variáveis Locais@>+=
#if defined(__linux__) || defined(BSD)
static Window window; // Estrutura da janela criada
#endif
@
O código que temos até agora cria a janela. Mas não a desenha na
tela. Não desenhar ela na tela automaticamente permite que nós
ajustemos atributos da janela antes que ela seja finalmente exibida.
A primeira coisa que precisaremos ajustar é que queremos que a janela
seja em tela-cheia por padrão. Faremos isso pedindo para o gerenciador
de janelas não interferir na criação da janela, colocando bordas ou
tentando limitar seu tamanho que iremos ajustar. Mas faremos isso só
se realmente formos iniciar no modo tela-cheia:
@<X11: Criar Janela@>+=
#if defined(__linux__) || defined(BSD)
#if !defined(W_WINDOW_NO_FULLSCREEN)
{
XSetWindowAttributes attributes;
XMoveWindow(display, window, 0, 0);
attributes.override_redirect = true;
XChangeWindowAttributes(display, window, CWOverrideRedirect,
&attributes);
}
#endif
#endif
@
Mas e se estivermos fora do modo de tela cheia e o usuário definiu
macros que dizem que o tamanho padrão da janela deve ser diferente de
ocupar a tela inteira? Neste caso, precisaremos redimencionar a janela
antes de desenhá-la na tela pela primeira vez. As macros que
controlarão o tamanho da janela quando não estamos em tela cheia
são \monoespaco{W\_WINDOW\_RESOLUTION\_X}
e \monoespaco{W\_WINDOW\_RESOLUTION\_Y}. Se elas valerem zero ou
menos, isso significa que o tamanho deve ser igual o da resolução da
tela. Caso contrário, seu valor representará o tamanho em pixels da
janela. Mas isso só se aplica quando não estamos em tela cheia:
@<X11: Criar Janela@>+=
#if defined(__linux__) || defined(BSD)
#if defined(W_WINDOW_NO_FULLSCREEN)
{
#if W_WINDOW_SIZE_X > 0
window_size_x = W_WINDOW_SIZE_X;
#else
window_size_x = screen_resolution_x;
#endif
#if W_WINDOW_SIZE_Y > 0
window_size_y = W_WINDOW_SIZE_Y;
#else
window_size_y = screen_resolution_y;
#endif
XResizeWindow(display, window, window_size_x, window_size_y);
}
#endif
#endif
@
Vamos também fixar o tamanho da janela para o atual para impedir que
ela de alguma forma seja redimencionada:
@<X11: Criar Janela@>+=
#if defined(__linux__) || defined(BSD)
{
XSizeHints hints;
hints.flags = PMinSize | PMaxSize;
#if defined(W_WINDOW_NO_FULLSCREEN) && W_WINDOW_SIZE_X > 0
hints.min_width = hints.max_width = W_WINDOW_SIZE_X;
#else
hints.min_width = hints.max_width = screen_resolution_x;
#endif
#if defined(W_WINDOW_NO_FULLSCREEN) && W_WINDOW_SIZE_Y > 0
hints.min_height = hints.max_height = W_WINDOW_SIZE_Y;
#else
hints.min_height = hints.max_height = screen_resolution_y;
#endif
XSetWMNormalHints(display, window, &hints);
}
#endif
@
O recurso acima requer o seguinte cabeçalho:
@<Headers@>+=
#if defined(__linux__) || defined(BSD)
#include <X11/Xutil.h>
#endif
@
Outra coisa relevante a ajustar é em que tipo de eventos nosso
programa quer prestar atenção quando estiver executando. Exemplo de
evento que não consideraremos interessante: o usuário move a janela
pela tela. Exemplo de evento interessante: o usuário pressiona um
botão enquanto nossa janela está ativa.
A lista de eventos que considerearemos importantes o bastante para que
nosso programa seja notificado é: janela é criada ou destruída,
usuário aperta ou solta botão de teclado, usuário aperta ou solta
botão do mouse e usuário move o mouse. Se não pedirmos para sermos
informados disso, nenhum evento será informado para nosso programa e
ele não saberá quando o usuário interage com ele por meio de mouse e
teclado.
@<X11: Criar Janela@>+=
#if defined(__linux__) || defined(BSD)
XSelectInput(display, window, StructureNotifyMask | KeyPressMask |
KeyReleaseMask | ButtonPressMask |
ButtonReleaseMask | PointerMotionMask);
#endif
@
Outra coisa importante é definir o nome da janela que será
criada. Geralmetne essa informação é apresentada de alguma forma pelo
gerenciador de janelas. Podemos deixar que o usuário escolha isso
ajustando a macro \monoespaco{W\_WINDOW\_NAME}:
@<X11: Criar Janela@>+=
#if defined(__linux__) || defined(BSD)
XStoreName(display, window, W_WINDOW_NAME);
#endif
@
Se esta macro não estiver definida, deixamos apenas uma string vazia:
@<Define Macros@>=
#if !defined(W_WINDOW_NAME)
#define W_WINDOW_NAME ""
#endif
@
Agora vamos configurar o OpenGL ES. Como isso é suficientemente
trabalhoso, colocamos os passos de como fazer isso na próxima sessão:
@<X11: Criar Janela@>+=
#if defined(__linux__) || defined(BSD)
@<X11: Configurar OpenGL ES@>
#endif
@
Uma vez que tenhamos ajustado as configurações de nossa janela,
podemos enfim começar a desenhar ela. Para isso enviamos uma
requisição para o servidor X e ficamos esperando em um laço até que
recebamos o evento de que a janela foi criada (já que pedimos para
sermos avisados deste evento passando a
flag \monoespaco{StructureNotifyMask} para a
função \monoespaco{XSelectInput} anteriormente). O código para isso é:
@<X11: Criar Janela@>+=
#if defined(__linux__) || defined(BSD)
XMapWindow(display, window);
{
XEvent e;
do{
XNextEvent(display, &e);
} while(e.type != MapNotify);
}
XSetInputFocus(display, window, RevertToParent, CurrentTime);
#endif
@
\subsubsecao{2.2.2. Criando uma Janela com Web Assembly}
Um dos ambientes mais diferentes nos quais nossa API pode executar
será navegadores de Internet após ter o código compilado para Web
Assembly. Neste caso, não há janelas verdadeiras, o espaço no qual
poderemos desenhar na tela e teremos controle será um ``canvas'' de
HTML. Isso faz com que não tenhamos que nos preocupar com a
possibilidade do usuário tentar redimencionar a janela, por
exemplo. Mas ainda deveremos permitir ajustes no tamanho do nosso
``canvas'' além de continuarmos precisando prestar atenção no tamanho
da tela.
Aqui iremos manipular a nossa área de desenho combinando duas coisas:
a biblioteca SDL, fornecida como interface para realizar ações
gráficas pelo ambiente Emscripten e também código Javascript que
poderemos executar para ajudar.
Primeiro vamos inserir o cabeçalho do Emscripten com os cabeçalhos SDL
e também cabeçalhos adicionais necessários por algumas macros que
iremos usar:
@<Cabeçalho OpenGL@>=
#if defined(__linux__) || defined(BSD)
#include <X11/X.h>
#endif
#if defined(__EMSCRIPTEN__)
#include <GLES3/gl3.h>
#include <SDL/SDL.h>
#include <emscripten.h>
#endif
@
Agora vamos ler a resolução da tela com a função definida na Subsubseção 2.1.2:
@<Web Assembly: Criar Janela@>=
#if defined(__EMSCRIPTEN__)
int screen_resolution_x, screen_resolution_y;
_Wget_screen_resolution(&screen_resolution_x, &screen_resolution_y);
#endif
@
A próxima coisa que temos a fazer é inicializar o sub-sistema de vídeo
da biblioteca SDL. Fazer isso é simplesmente chamar a função de
inicialização:
@<Web Assembly: Criar Janela@>+=
#if defined(__EMSCRIPTEN__)
SDL_Init(SDL_INIT_VIDEO);
#endif
@
Agora vamos efetivamente criar a janela, o que na prática ajusta o
tamanho do canvas HTML onde iremos desenhar e o inicializa. O
``canvas'' HTML deverá ter o tamanho da janela, a menos que exista
definida a macro \monoespaco{W\_WINDOW\_NO\_FULLSCREEN} com valores
positivos para \monoespaco{W\_WINDOW\_RESOLUTION\_X}
e \monoespaco{W\_WINDOW\_RESOLUTION\_Y}, caso em que usaremos esses
valores como tamanho. Também iremos nos certificar de que o canvas
está realmente visível, pois ele pode ter sido oculto (é o que fazemos
com ele se executarmos a função de fechar janela).
@<Web Assembly: Criar Janela@>+=
#if defined(__EMSCRIPTEN__)
{
fullscreen_mode = true;
double pixel_ratio = EM_ASM_DOUBLE({
return window.devicePixelRatio;
});
window_size_x = EM_ASM_INT({
return window.innerWidth * window.devicePixelRatio;;
});
window_size_y = EM_ASM_INT({
return window.innerHeight * window.devicePixelRatio;;
});
#if defined(W_WINDOW_NO_FULLSCREEN)
fullscreen_mode = false;
#if defined(W_WINDOW_SIZE_X) && W_Window_Size_X > 0
window_size_x = W_Window_Size_X;
#endif
#if defined(W_WINDOW_SIZE_Y) && W_Window_Size_Y > 0
window_size_y = W_Window_Size_Y;
#endif
#endif
// Ajusta versão do OpenGL
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION,
W_WINDOW_OPENGL_MAJOR_VERSION);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION,
W_WINDOW_OPENGL_MINOR_VERSION);
// Garante que o canvas estará visível
EM_ASM(
var el = document.getElementById("canvas");
el.style.display = "initial";
);
window = SDL_SetVideoMode(window_size_x / pixel_ratio,
window_size_y / pixel_ratio, 0, SDL_OPENGL);
if(fullscreen_mode){
EM_ASM(
var el = document.getElementById("canvas");
el.style.position = "absolute";
el.style.top = "0px";
el.style.left = "0px";
el.style.width = window.innerWidth + "px";
el.style.height = window.innerHeight + "px";
el.width = (window.innerWidth * window.devicePixelRatio);
el.height = (window.innerHeight * window.devicePixelRatio);
);
}
if(window == NULL)
return false;
glViewport(0, 0, window_size_x, window_size_y);
}
#endif
@
No código acima, depois de criar a ``janela'' (que é um ``canvas'') também
especificamos o tamanho de nossa área de exibição, ou ``viewport''. Na
maioria das vezes não é necessário ajustá-lo quando a área em que
iremos renderizar é igual à de nossa janela. Contudo, quando o código
acima é executado em ambiente móvel, como em celulares onde o pixel
lógico é diferente do pixel físico da tela, o valor padrão da tela de
exibição pode vir errado. Por isso é importante ajustar manualmente
nestes casos.
Outra coisa necessária para o código acima é uma variável estática
booleana que mantém se estamos em modo tela-cheia ou não:
@<Variáveis Locais@>+=
#if defined(__EMSCRIPTEN__)
static bool fullscreen_mode = false;
#endif
@
A variável é necessária caso estejamos rodando em um navegador porque
neste caso, somos nós e não um gerenciador de janelas externo que
precisa fazer alguns ajustes na nossa ``janela'' para deixá-la em tela
cheia. Além disso, também tentamos ajustar nossa janela para a
tela-cheia mesmo que o navegador de Internet não nos deixe entrar em
modo de tela-cheia: neste caso ainda deixamos a nossa área ficar com o
tamanho da tela e ocupar o topo do documento para que o usuário possa
ainda entrar em tela-cheia manualmente. Então somente no caso de
ambiente Web Assembly, nós memorizamos em uma variável se devemos
estar ou não em tela-cheia.
O que chamamos de ``janela'' neste caso é considerada uma superfície
SDL mapeada para um canvas HTML:
@<Variáveis Locais@>+=
#if defined(__EMSCRIPTEN__)
static SDL_Surface *window;
#endif
@
\subsubsecao{2.2.3. Criando uma Janela no Windows}
Como nos códigos anteriores, começamos lendo a resolução da tela:
@<Windows: Criar Janela@>=
#if defined(_WIN32)
int screen_resolution_x, screen_resolution_y;
_Wget_screen_resolution(&screen_resolution_x, &screen_resolution_y);
#endif
@
A próxima coisa a fazer é definir uma classe para a janela que iremos
criar. Primeiro vamos precisar dar um nome arbitrário para ela no
formato de uma string qualquer. Iremos chamá-la de ``WeaverWindow'':
@<Variáveis Locais@>+=
#if defined(_WIN32)
static const char *class_name = "WeaverWindow";
#endif
@
Agora precisamos para nossa classe uma função que irá tratar todos os
sinais e mensagens enviada para nossa janela. Mensagens são enviadas e
devem ser tratadas quando a janela é criada, destruída,
redimencionada, exposta na tela, etc. Na dúvida sempre podemos
repassar cada mensagem para a função
padrão \monoespaco{DefWindowProc}, mas algumas coisas nós mesmos
teremos que definir. O formato da função que tratará as mensagens
recebidas pela janela é:
@<Funções da API@>+=
#if defined(_WIN32)
LRESULT CALLBACK WindowProc(HWND window, UINT msg, WPARAM param1, LPARAM param2){
switch(msg){
@<Windows: Trata Mensagens para Janela@>
default:
return DefWindowProc(window, msg, param1, param2);
}
}
#endif
@
Mas em quais casos iremos tratar as mensagens ao invés de apenas
passá-las adiante para o \monoespaco{DefWindowProc}? Um dos casos é
quando a janela receber uma mensagem para ser fechada:
@<Windows: Trata Mensagens para Janela@>=
case WM_DESTROY:
PostQuitMessage(0);
return 0;
break;
@
Agora temos que criar uma classe para a janela que iremos criar. Ela
deve apenas ter um nome único que não conflite com nomes padrão usados
pelo sistema. Também temos que passar na criação da classe um
identificador do programa que executamos (que obtemos
com \monoespaco{GetModuleHandle} e a função que irá lidar com
mensagens e sinais recebidos pela janela (no
caso, o padrão \monoespaco{DefWindowProc}).
@<Windows: Criar Janela@>+=
#if defined(_WIN32)
if(!already_created_a_class){
ATOM ret;
WNDCLASS window_class;
memset(&window_class, 0, sizeof(WNDCLASS));
window_class.lpfnWndProc = WindowProc;
window_class.hInstance = GetModuleHandle(NULL);
window_class.lpszClassName = class_name;
window_class.hbrBackground = CreateSolidBrush(RGB(0, 0, 0)); // Janela preta
window_class.hCursor = LoadCursor(NULL, IDC_ARROW); // Cursor normal
ret = RegisterClass(&window_class);
if(ret == 0){
#if defined(W_DEBUG_WINDOW)
fprintf(stderr, "ERROR: Failed to register Window Class. SysError: %d\n",
GetLastError());
#endif
return false;
}
already_created_a_class = true;
}
#endif
@
Por conveniência usamos a função \monoespaco{memset} para inicializar
a estrutura da classe da janela, já que a maior parte de seus
elementos pode ser mantida como zero, que é o padrão. Por isso,
inserimos o cabeçalho abaixo:
@<Cabeçalhos@>+=
#if defined(_WIN32)
#include <string.h>
#endif
@
O código acima presume que temos declarada a seguinte variável que
armazena se nossa classe já foi criada:
@<Variáveis Locais@>+=
#if defined(_WIN32)
static bool already_created_a_class = false;