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).