123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241(** put a sublayout on top of the main layout *)(* recall that "on top" means "insert_after" layer; change ? *)(* TODO implement the resize function *)openB_utilsmoduleLayout=B_layoutmoduleWidget=B_widgetmoduleChain=B_chainmoduleTheme=B_thememoduleTimeout=B_timeoutmoduleTrigger=B_triggermoduleDraw=B_drawmoduleStyle=B_stylemoduleBox=B_boxletnew_layer_abovebase=printddebug_graphics"Create new layer";Chain.insert_afterbase(Draw.new_layer())(* search top_layer inside the layout. *)(* use the global toplayer instead (Chain.last) or at least the top_layer of the
whole connected component ? (top_layer (Layout.top_house layout)). In fact
since the top_house is supposed to contain all the graphics of the window,
and there is one layer chain per window, the two choices should give the same
answer. Thus it's better to use Chain.last layer. OK see below ... *)letrectop_layerlayout=letopenLayoutinmatchlayout.contentwith|Resident_->get_layerlayout|Roomsr->matchlist_maxChain.compare(List.maptop_layerr)with|None->printddebug_error"Error: there should be a top layer";get_layerlayout|Somel->lletglobal_top_layerlayout:Draw.layer=Chain.last(Layout.get_layerlayout)(* Register a resize function that will follow (size AND position) the model
layout. For the position to work correctly, both layouts must be in the same
house. *)letresize_same_asmodelroom=letresize_=letopenLayoutinmatchmodel.house,room.housewith|Someh1,Someh2whenLayout.equalh1h2->letkeep_resize=trueinletsize=get_sizemodelinletx=xposmodelinlety=yposmodelinsetx~keep_resizeroomx;sety~keep_resizeroomy;set_size~keep_resizeroomsize|_->printddebug_error"[resize_same_as] must apply to two rooms in the same house. Maybe \
you should use [Layout.resize_follow_house]."inroom.resize<-resize(* create a box of the dimension of the layout *)letfilter_screen?color?layer?keyboard_focuslayout=letw,h=Layout.(widthlayout,heightlayout)inprintddebug_graphics"Create filter screen (%d,%d)"wh;letb=matchcolorwith|None->Widget.empty~w~h()|Somecolor->letstyle=Style.create~background:(Style.Solidcolor)()inWidget.box~w~h~style()inletscreen=(* Layout.(flat_of_w ~sep:0 layout.canvas [b]) in *)Layout.(resident~name:"filter"?canvas:layout.canvasb)in(* Layout.(screen.geometry <- {screen.geometry with w; h}); *)do_optionlayer(Layout.set_layerscreen);screen.Layout.keyboard_focus<-keyboard_focus;screen(** add a screen on top of the layout. This can be useful to make the whole
layout clickable as a whole. To make sure this works as expected, it should
be called dynamically and not statically before running the board, because if
other layers are created afterwards, the screen might endup not being on top
of everything. *)letadd_screen?(color=Draw.(transpred)(* DEBUG *))layout=letbase_layer=top_layerlayoutinletscreen_layer=new_layer_abovebase_layerinletscreen=filter_screen~color~layer:screen_layerlayoutinLayout.add_room~dst:layoutscreen;Layout.resize_follow_housescreen;screen(* TODO add dx dy *)letattach_on_top?(dx=0)?(dy=0)houselayout=letopenLayoutinsetxlayout(getxlayout+dx);setylayout(getylayout+dy);global_set_layerlayout(global_top_layerhouse);add_room~dst:houselayout;resize_same_ashouselayout(** add two layers on top of the house: one for the screen to hide the house,
one for the layout on top of the screen. Return the screen. *)(* TODO: use add_screen to reduce code *)letattach?bg?(show=true)houselayout=letbase_layer=global_top_layerhousein(* eg. 10 *)letfilter_layer=new_layer_abovebase_layerin(* eg. 20 *)lettop_layer=new_layer_abovefilter_layerin(* eg. 30 *)(* We change layer for layout and all its children: *)Layout.global_set_layerlayouttop_layer;letscreen=filter_screen?color:bghouseinLayout.set_layerscreenfilter_layer;Layout.add_room~dst:housescreen;Layout.scale_resizescreen;Layout.add_room~halign:Draw.Center~valign:Draw.Center~dst:houselayout;Layout.scale_resizelayout;screen.Layout.show<-show;layout.Layout.show<-show;Trigger.push_keyboard_focus(screen.Layout.id);Trigger.push_mouse_focus(screen.Layout.id);(* redundant *)(* When inserting new elements on the fly, one needs to ask to mouse to
refresh its focus, see b_main.ml. *)screen(* some predefined popup designs *)letslide_in~dstcontentbuttons=letstyle=Style.(create~border:(mk_border(mk_line~color:Draw.(opaquegrey)()))~shadow:(mk_shadow())~background:(SolidDraw.(opaque(palegrey)))())inletbackground=Layout.Box(Box.create~style())inletpopup=Layout.tower~align:Draw.Center~background[content;buttons]inletscreen=attach~bg:(Draw.(set_alpha200(palegrey)))dstpopupin(* Layout.slide_in ~dst popup; *)popup,screenletone_button?w?h~button~dstcontent=letclose_btn=Widget.button~border_radius:3buttoninletpopup,screen=slide_in~dstcontent(Layout.resident?w?hclose_btn)inletclose_=Layout.hidepopup;Layout.hidescreen;Layout.fade_outscreeninWidget.on_release~release:closeclose_btn(* a text and a close button. *)(* TODO the ?w and ?h define the size of the text_display (not automatically
detected). It should also include the size of the close button *)letinfo?w?h?(button="Close")textdst=lettd=Widget.text_display?w?htext|>Layout.residentinone_button?w?h~button~dsttd(* ?w and ?h to specify a common size for both buttons *)lettwo_buttons?w?h~label1~label2~action1~action2contentdst=letbtn1=Widget.button~border_radius:3label1inletbtn2=Widget.button~border_radius:3label2inletbuttons=Layout.(flat~vmargin:0~sep:(2*Theme.room_margin)[resident?w?hbtn1;resident?w?hbtn2])inletpopup,screen=slide_in~dstcontentbuttonsinletclose()=(*Layout.hide popup;*)Layout.fade_out~hide:truepopup;(*Layout.hide screen*)Layout.fade_out~hide:truescreeninletdo1_=close();action1()inletdo2_=close();action2()inWidget.on_release~release:do1btn1;Widget.on_release~release:do2btn2letyesno?w?h?(yes="Yes")?(no="No")~yes_action~no_actiontextdst=lettd=Widget.text_display?w?htext|>Layout.residentintwo_buttons?w?h~label1:yes~label2:no~action1:yes_action~action2:no_actiontddst(* tooltips *)(* tooltips are small popups which are displayed on a specified layout, close to
a specified target. The target should be a room inside the layout. Tooltips
don't have 'screens' like usual popups because they don't prevent the user to
do anything. Tooltips are displayed when the "mouse_at_rest" event is
triggered to the specified widget, and removed when the "mouse_leave" event is
triggered. *)(* the content of the tooltips is usually a simple text, but it can be any
layout *)typeposition=|LeftOf|RightOf|Above|Below|Mouselettooltip?background?(position=Below)text~targetwidgetlayout=lett=Widget.label~size:Theme.small_font_sizetextinletbackground=matchbackgroundwith|Someb->b|None->letstyle=Style.(create~border:(mk_border~radius:5(mk_line()))~background:(SolidDraw.(opaque(palegrey)))())inLayout.Box(Box.create~style())inlettooltip=Layout.tower_of_w~sep:3~background[t]inattach_on_toplayouttooltip;tooltip.Layout.show<-false;letshow_tooltip___=letopenLayoutinifnottooltip.showthenbeginletx,y=pos_fromlayouttargetin(* print_endline (Printf.sprintf "(%i,%i)" x y); *)letx',y'=matchpositionwith|Below->x,y+(heighttarget)+2|Above->x,y-(heighttooltip)-2|LeftOf->x-(widthtooltip)-2,y|RightOf->x+(widthtarget)+2,y|Mouse->letx,y=Mouse.pos()in(x+8,y+8)insetytooltipy';setxtooltipx';tooltip.show<-true;Layout.fade_intooltipendinletto_show=reftrueinlethide_tooltipb=to_show:=false;ignore(Timeout.add200(fun()->tooltip.Layout.show<-!to_show;Trigger.push_redraw(Widget.idb)))inletenter_=iftooltip.Layout.showthento_show:=truein(* this amounts to cancelling the timeout, which is
what we want to do when we re-enter the target *)Widget.mouse_over~enter~leave:hide_tooltipwidget;letc=Widget.connect_mainwidgetwidgetshow_tooltip[Trigger.mouse_at_rest]inWidget.add_connectionwidgetc