host_window.anubis
27.4 KB
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
The Anubis Project.
A Widget System (4th version).
Opening a host window containing widgets.
Copyright (c) Alain Proute' 2005.
Authors: Alain Proute'
This file is the right entry point if you want to open a host window (window of the
host operating system) containing a tree of widgets. If you want to create a new kind
of widget, see 'widget.anubis' in the same directory.
------------------------------------ Table of Contents --------------------------------
*** (1) Creating a host window containing a tree of widgets.
*** (1.1) Host window states.
*** (1.2) Opening the window.
*** (2) The content of the window.
*** (3) The state of the machine.
---------------------------------------------------------------------------------------
read tools/basis.anubis
read system/string.anubis
read widget.anubis
*** (1) Creating a host window containing a tree of widgets.
In this file several variants of the function 'open_host_window' are defined. When one
of them is executed, a window of the host operating system is opened, and its content
(a tree of widgets) is displayed.
*** (1.1) Host window states.
We define 'host window states' (not to be confused with 'state variables'; see
below). They are of the following type:
public type WidgetHostWindowState:
host_window_state
(
Var(Word32) window_width_v, // size of host window
Var(Word32) window_height_v,
Var(Word32) window_x_v, // position on screen
Var(Word32) window_y_v,
Var(String) title_v // title of host window
).
Widgets may read the content of these variables. They may also assign new values to
these variable, to get the corresponding effect.
*** (1.2) Opening the window.
When the host window is opened, a host window state is created, which needs initial
values. Actually, you provide only the root widget, the position ('x' and 'y') on
screen and the title. The initial size of the host window is computed according to the
initial size of the root widget.
public define Bool
open_host_window
(
Widget root, // content of host window
Word32 x, // initial position on screen
Word32 y,
String title // initial title
).
If your widget needs to use the host window state, use the following:
public define Bool
open_host_window
(
WidgetHostWindowState -> Widget root, // content of host window
Word32 x, // initial position on screen
Word32 y,
String title // initial title
).
Below are variants which centers the window on the screen when it is created:
public define Bool
open_host_window
(
Widget root, // content of host window
String title
).
public define Bool
open_host_window
(
WidgetHostWindowState -> Widget root, // content of host window
String title
).
*** (2) The content of the window.
Various sorts of widgets are defined in the files of the directory 'library/widgets4'.
You can construct the content of the host window using these widgets. There are
'tables', 'buttons', 'input fields', 'images', etc... For each kind of widget, see the
public part of the corresponding file.
The widget you construct may receive the host window state when the window is opened,
so that widgets have access to the variables in the host window state.
*** (3) The state of the machine.
The role of the widgets is to show and allow the modification of the 'state' of some
'machine'. This state should be stored into a set of dynamic variables. These dynamic
variables must be monitored so that the widgets may reflect this state in real
time. For the reason that widgets are highly interdependant, this monitoring cannot be
performed by the widgets themself. It must be performed by the widget system. This is
the reason why these variables must be registered in a particular way.
You should create the state variables and the content widget before opening the host
window. Proceed as follows:
with var_1 = var(...), // create the state variables
var_2 = // (or get them from anywhere you want)
...
content = create_...(...), // create a tree of widgets
open_host_window("My Window", // open the host window
content)
You must create the state variables before the widgets, because the widgets will depend
on the state variables. After having created the variables (or got them from anywhere
else), you may of course construct several trees of widgets and open several windows.
--- That's all for the public part ! --------------------------------------------------
read tools.anubis
------------------------------------ Table of Contents --------------------------------
*** [2] The paint method.
*** [] Handling events.
*** [] Translating widget answers into lists of rectangles.
*** [] The event handler.
*** Events compression.
*** [] Opening the host window.
---------------------------------------------------------------------------------------
*** [2] The paint method.
The host window requires a 'paint method' of type '(HostWindow,Rectangle) -> One' (see
'predefined.anubis'). We use a double buffer technique in order to void blinking. The
'double buffer' is just an image in the format of the host system (hence of type
'HostImage').
Since the size of the host window may change, the size of the double buffer should
change accordingly. However, we don't want to be faced to a problem of memory when the
host window is resized. For this reason, we use a fixed size buffer, which is allocated
(created) when the host window is created. If the host window is either wider or higher
than the buffer, the buffer is used several times so as to cover the whole window. So,
this is a 'moving buffer'. A good compromise may be to create a buffer of the same size
as the initial size of the (client area) of the host window.
Painting occurs within an area which is an union of rectangles. Indeed, when painting
is needed, we try to avoid to repaint the whole window. We repaint only within an area
as small as possible. This area is called the 'clipping area'. Now, since the window
may be bigger than the double buffer, we must divide the window into rectangles of the
size of the buffer, and repaint within each rectangle. Of course, for each rectangle
of this division, we first compute the intersection of the clipping area and the
rectangle.
In order to avoid to perform too many tree walks, we want to handle one rectangle of
the division of the host window with only one tree walk. For this reason, the redraw
functions of each widget receives an area as a list of rectangles, within which
redrawing must occur. This area (in absolute coordinates) is stored in the draw tool
box (see 'widget.anubis').
Note: the 'root widget' is assumed to adapt its size automatically to the size of the
window. This widget may monitor some variables in the host window state if needed.
read trace.anubis
define (HostWindow,List(Rectangle)) -> One
widget_paint_method
(
HostImage buffer,
WidgetHostWindowState -> Widget root,
WidgetHostWindowState the_window_state
) =
(HostWindow win, List(Rectangle) clipping_area) |->
with root_widget = root(the_window_state),
//trace(clipping_area);
if size(root_widget) is (root_width,root_height) then
if size(win) is (win_width,win_height) then
(if (win_width = root_width & win_height = root_height)
then unique
else resize(win,root_width,root_height));
if size(buffer) is (buffer_width,buffer_height) then
with division = divide((Rectangle)rect(0,0,root_width,root_height),
buffer_width,buffer_height),
map_forget((Rectangle dr) |->
(if dr is rect(bx,by,bu,bv) then
with new_clipping = intersection(dr,clipping_area),
draw(draw_tool_box(buffer,bx,by,0,0,
new_clipping))(root_widget,0,0);
map((Rectangle r) |-> paint_image(win,r,bx,by,buffer),new_clipping)),
division).
*** [] Handling events.
Events of the host window (as defined in 'predefined.anubis') are not the same as
widget events (see 'widget.anubis'). Hence, the event handler must first filter the
host window events, handle some of them, and transmit an appropriate translation of the
others to the root widget. When the answer comes back, it must be tranformed into a
list of rectangles.
*** [] Translating widget answers into lists of rectangles.
define List(Rectangle)
translate
(
HostWindow win,
WidgetAnswer a,
Var(Maybe(Var(One))) mouse_capture_ticket_v,
Var(Maybe(Var(One))) keyboard_capture_ticket_v,
Var(WidgetEventCompression) compress_v,
Var(Maybe(WidgetEvent)) secondary_event_v
) =
if a is
{
not_handled(ar) then if ar is abs_area(l) then l,
handled(ar) then if ar is abs_area(l) then l,
resized then if size(win) is (w,h) then [rect(0,0,w,h)],
ignored then [],
want_to_capture_mouse(v,cm,ar) then mouse_capture_ticket_v <- success(v);
compress_v <- cm;
//print("Mouse captured.\n");
if ar is abs_area(l) then l,
want_to_capture_keyboard(v,ar) then keyboard_capture_ticket_v <- success(v);
secondary_event_v <-
success(keyboard_recaptured((Var(One) t) |-> v = t));
if ar is abs_area(l) then l
}.
*** [] The event handler.
define (HostWindow,HostWindowEvent(One)) -> List(Rectangle)
widget_event_handler
(
WidgetHostWindowState -> Widget root,
WidgetMVToolBox mvtb,
Var(Maybe(Var(One))) mouse_capture_ticket_v,
Var(Maybe(Var(One))) keyboard_capture_ticket_v,
Var(WidgetEventCompression) compress_v,
Var(Word32) pmx_v,
Var(Word32) pmy_v,
Var(Maybe(WidgetEvent)) secondary_event,
WidgetHostWindowState the_window_state,
) =
//
// We store all rectangles in the next variable, and only the 'tick' event
// returns a non empty list of rectangles.
//
//with store_v = var((List(Rectangle))[]),
(HostWindow win, HostWindowEvent(One) we) |->
with root_widget = root(the_window_state),
if we is
{
quit then
forget(transmit(event_tool_box(win,0,0,*pmx_v,*pmy_v))(root_widget,0,0,quit));
[],
expose then
//
// The window system will repaint the window after this event. Hence we have
// no rectangle to return, but we must update the variables in WidgetHostWindowState
//
if the_window_state is host_window_state(ww_v,wh_v,wx_v,wy_v,_) then
//print("*ww_v = "+*ww_v+" *wh_v = "+*wh_v+"\n");
if size(win) is (winw,winh) then (//print("winw = "+winw+" winh = "+winh+"\n");
ww_v <- winw; wh_v <- winh);
[],
pointer_entering then
//mouse_capture_ticket_v <- failure;
[],
pointer_leaving then
//mouse_capture_ticket_v <- failure;
with result = translate(win,
transmit(event_tool_box(win,0,0,*pmx_v,*pmy_v))(root_widget,0,0,
mouse_gone),
mouse_capture_ticket_v,
keyboard_capture_ticket_v,
compress_v,secondary_event),
//store_v <- *store_v + result;
//[],
result,
key_down(ks,kk) then
(
if mouse_left(ks)
then unique
else mouse_capture_ticket_v <- failure
);
if *keyboard_capture_ticket_v is
{
failure then [],
success(ticket) then
with result = translate(win,
transmit(event_tool_box(win,0,0,*pmx_v,*pmy_v))(root_widget,0,0,
key_down((Var(One) t) |->
if t = ticket
then success((ks,kk))
else failure)),
mouse_capture_ticket_v,
keyboard_capture_ticket_v,
compress_v,secondary_event),
//store_v <- *store_v + result;
//[]
result
},
mouse_move(ks,x,y) then
(
if mouse_left(ks)
then unique
else mouse_capture_ticket_v <- failure
);
if *mouse_capture_ticket_v is
{
failure then
/* mouse is not currently captured */
with result = translate(win,
transmit(event_tool_box(win,0,0,*pmx_v,*pmy_v))(root_widget,0,0,
mouse_move(ks,x,y)),
mouse_capture_ticket_v,
keyboard_capture_ticket_v,
compress_v,secondary_event),
pmx_v <- x;
pmy_v <- y;
//store_v <- *store_v + result;
//[],
result,
success(capture_ticket) then
/* mouse is currently captured */
with result = translate(win,
transmit(event_tool_box(win,0,0,*pmx_v,*pmy_v))(root_widget,0,0,
captured_mouse_move(
(Var(One) t,
WidgetEventToolBox etb) |->
if t = capture_ticket
then (if etb is event_tool_box(win,wx,wy,_,_) then
success((x-wx,y-wy)))
else failure)),
mouse_capture_ticket_v,
keyboard_capture_ticket_v,
compress_v,secondary_event),
pmx_v <- x;
pmy_v <- y;
//store_v <- *store_v + result;
//[],
result,
},
mouse_click(ks,mc,x,y) then
(
if mouse_left(ks)
then unique
else //print("Mouse released.\n");
mouse_capture_ticket_v <- failure
);
with result =
translate(win,
transmit(event_tool_box(win,0,0,*pmx_v,*pmy_v))(root_widget,0,0,
if *mouse_capture_ticket_v is success(ticket)
then (if mc = left_up
then captured_mouse_liberated(
(Var(One) t, WidgetEventToolBox etb) |->
if t = ticket
then (if etb is event_tool_box(win,wx,wy,_,_) then
success((x-wx,y-wy)))
else failure)
else mouse_click(ks,mc,x,y))
else mouse_click(ks,mc,x,y)),
mouse_capture_ticket_v,
keyboard_capture_ticket_v,
compress_v,secondary_event),
pmx_v <- x;
pmy_v <- y;
//store_v <- *store_v + result;
//[],
result,
mouse_wheel(ks,d,x,y) then
(
if mouse_left(ks)
then unique
else mouse_capture_ticket_v <- failure
);
with result = translate(win,
transmit(event_tool_box(win,0,0,*pmx_v,*pmy_v))(root_widget,0,0,
mouse_wheel(ks,d,x,y)),
mouse_capture_ticket_v,
keyboard_capture_ticket_v,
compress_v,secondary_event),
pmx_v <- x;
pmy_v <- y;
//store_v <- *store_v + result;
//[],
result,
tick then
if mvtb is mv_tool_box(add_id,collect) then
with l = collect(unique),
//with last =
//*store_v +
if l is
{
[ ] then [ ],
[_ . _] then
translate(win,
transmit(event_tool_box(win,0,0,0,0))(root_widget,0,0,
changed(l)),
mouse_capture_ticket_v,
keyboard_capture_ticket_v,
compress_v,secondary_event)
} +
if *secondary_event is
{
failure then [],
success(e) then
secondary_event <- failure;
translate(win,
transmit(event_tool_box(win,0,0,*pmx_v,*pmy_v))(root_widget,0,0,e),
mouse_capture_ticket_v,
keyboard_capture_ticket_v,
compress_v,secondary_event)
},
//store_v <- [];
//last,
repaint(l) then
//store_v <- *store_v + l;
//[],
l,
specific(se) then
[]
}.
*** Events compression.
Events compression is necessary, because in general too many window events arrive. The
generic host window handler (see 'predefined.anubis') recovers window events not one
after the other but all available events in a list of events (in chronological
order). This list must be optimized (compressed).
The compression is a kind of rewritting of the list. We have choosen the following
rewrite rules:
[expose . t] --> [expose] because expose redraws the whole window
[mouse_move(ks1,x1,y1), mouse_move(ks2,x2,y2) . t] --> [mouse_move(ks2,x2,y2) . t]
define List(HostWindowEvent(One))
compress_events
(
List(HostWindowEvent(One)) l
) =
if l is
{
[ ] then [ ],
[e1 . es1] then
if e1 is expose then [expose] else
if e1 is pointer_entering then compress_events(es1) else
if e1 is mouse_move(ks1,x1,y1) then if es1 is
{
[ ] then l,
[e2 . es2] then if e2 is mouse_move(ks2,x2,y2)
then compress_events(es1)
else [e1 . compress_events(es1)]
}
else [e1 . compress_events(es1)]
}.
define Bool
has_mouse_move
(
List(HostWindowEvent(One)) l
) =
if l is
{
[] then false,
[e . es] then if e is mouse_move(_,_,_) then true else has_mouse_move(es)
}.
define List(HostWindowEvent(One))
compress_events1
(
List(HostWindowEvent(One)) l
) =
if l is
{
[] then [],
[e . es] then with es1 = compress_events1(es),
if e is
{
quit then [quit],
expose then [expose],
pointer_entering then if member(es1,e) then es1 else [e . es1],
pointer_leaving then if member(es1,e) then es1 else [e . es1],
key_down(_,_) then [e . es1],
mouse_move(_,_,_) then if has_mouse_move(es1) then es1 else [e . es1],
mouse_click(_,_,_,_) then [e . es1],
mouse_wheel(_,_,_,_) then [e . es1],
tick then [e . es1],
repaint(_) then [e . es1],
specific(_) then [e . es1]
}
}.
define List(HostWindowEvent(One))
compress_events
(
List(HostWindowEvent(One)) l
) =
with n = length(l),
result = compress_events1(l),
//print(n+" ---> "+length(result)+"\n");
result.
*** [] Opening the host window.
public define Bool
open_host_window
(
WidgetHostWindowState -> Widget root, // content of host window
Word32 x, // coordinates of window on screen
Word32 y,
String title // title of window
) =
with the_window_state =
host_window_state(var((Word32)1000), // size is updated below
var((Word32)700),
var(x),
var(y),
var(title)),
root_widget = root(the_window_state),
if size(root_widget) is (root_w,root_h) then
window_width_v(the_window_state) <- root_w;
window_height_v(the_window_state) <- root_h;
with regs = registrations(root_widget)(unique),
monitoring_v = var((List(Word32))[]),
mvtb = make_mv_tool_box(monitoring_v),
map_forget((WidgetRegistration wrg) |-> if wrg is
registration(f) then f(mvtb),
regs);
with buffer = to_host_image(create_rgba_image(800,800,
rgba(0,0,0,0)),1),
with mouse_capture_ticket_v = var((Maybe(Var(One)))failure),
keyboard_capture_ticket_v = var((Maybe(Var(One)))failure),
compress_v = var(compress),
with pmx_v = var((Word32)0),
pmy_v = var((Word32)0),
with secondary_event = var((Maybe(WidgetEvent))failure),
if open_host_window(rect(x,y,x+root_w,y+root_h),
title,
managed(resizable),
(HostWindow win, List(Rectangle) rs) |->
forget(regs);
with start = (UTime)unow,
widget_paint_method(buffer,root,the_window_state)(win,rs),
//if unow - start is utime(s,m) then
//print("draw: "+s+","+zero_pad_n(6,m)+" seconds\n"),
(HostWindow w, HostWindowEvent(One) e) |->
with start = (UTime)unow,
result = widget_event_handler(root,
mvtb,
mouse_capture_ticket_v,
keyboard_capture_ticket_v,
compress_v,
pmx_v,
pmy_v,
secondary_event,
the_window_state)(w,e),
//if unow - start is utime(s,m) then
//(if e is tick then unique else
//print("event: "+s+","+zero_pad_n(6,m)+" seconds\n"));
result,
(List(HostWindowEvent(One)) l) |->
if *compress_v is
{
compress then compress_events(l),
dont_compress then l
}) is
{
failure then print("Cannot open the window '"+title+"'.\n");
false,
success(win) then //print("Root widget size: "+root_w+" "+root_h+"\n");
//print(if size(win) is (w,h) then "Window size: "+w+" "+h+"\n");
//print(if screen_size is (w,h) then "Screen size: "+w+" "+h+"\n");
show(win);
true
}.
The variants:
public define Bool
open_host_window
(
Widget root, // content of host window
Word32 x,
Word32 y,
String title
) =
open_host_window((WidgetHostWindowState hws) |-> root,x,y,title).
public define Bool
open_host_window
(
WidgetHostWindowState -> Widget root, // content of host window
String title
) =
with the_window_state =
host_window_state(var((Word32)1000), // size is updated below
var((Word32)700),
var(0),
var(0),
var(title)),
root_widget = root(the_window_state),
if size(root_widget) is (rw,rh) then
if screen_size is (sw,sh) then
open_host_window(root,
max(0,(sw-rw)>>1),
max(0,(sh-rh)>>1),
title).
public define Bool
open_host_window
(
Widget root, // content of host window
String title
) =
open_host_window((WidgetHostWindowState hws) |-> root,title).