Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file layers.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543(*********************************************************************************)(* OCaml-Stk *)(* *)(* Copyright (C) 2023-2024 INRIA All rights reserved. *)(* Author: Maxence Guesdon, INRIA Saclay *)(* *)(* This program is free software; you can redistribute it and/or modify *)(* it under the terms of the GNU General Public License as *)(* published by the Free Software Foundation, version 3 of the License. *)(* *)(* This program is distributed in the hope that it will be useful, *)(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)(* GNU General Public License for more details. *)(* *)(* You should have received a copy of the GNU General Public *)(* License along with this program; if not, write to the Free Software *)(* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA *)(* 02111-1307 USA *)(* *)(* As a special exception, you have permission to link this program *)(* with the OCaml compiler and distribute executables, as long as you *)(* follow the requirements of the GNU GPL in regard to all of the *)(* software in the executable aside from the OCaml compiler. *)(* *)(* Contact: Maxence.Guesdon@inria.fr *)(* *)(*********************************************************************************)(** Multi-layer widget. *)[@@@landmark"auto"]openTsdlmoduleA=Dynarray(** {2 Properties} *)(** This type describe how SDL events are propagated through layers.*)typeevent_direction=|Upward(* events are handled from bottom layer up to top layer *)|Downward(* events are handle from top layer down to bottom layer *)letstring_of_event_direction=function|Upward->"upward"|Downward->"downward"letevent_direction_of_stringstr=matchString.lowercase_asciistrwith|"upward"->Upward|"downward"->Downward|_->Log.warn(funm->m"Invalid event_direction %S"str);UpwardmoduleTEvent_direction=structtypet=event_directionletcompare=Stdlib.compareletwrapper=letto_json?with_docx=`String(string_of_event_directionx)inletfrom_json?def=function|`Strings->event_direction_of_strings|json->Ocf.invalid_valuejsoninSome(Ocf.Wrapper.maketo_jsonfrom_json)lettransition=NoneendmodulePEvent_direction=Props.Add_prop_type(TEvent_direction)(** Property used to store event direction. *)letevent_direction:event_directionProps.prop=PEvent_direction.mk_prop~inherited:false~default:Upward"event_direction"(**/**)letarray_do_opt_until_true=letreciterftleni=ifi>=lenthenfalseelsef(A.getti)||iterftlen(i+1)infunft->iterft(A.lengtht)0letarray_do_until_trueft=letg=functionNone->false|Somex->fxinarray_do_opt_until_truegtletarray_do_opt_until_true_rev=letreciterfti=ifi<0thenfalseelsef(A.getti)||iterft(i-1)infunft->iterft(A.lengtht-1)letarray_do_until_true_revft=letg=functionNone->false|Somex->fxinarray_do_opt_until_true_revgtletarray_doi_opt_until_true=letreciterftleni=ifi>=lenthenfalseelsefi(A.getti)||iterftlen(i+1)infunft->iterft(A.lengtht)0letarray_doi_until_trueft=letgi=functionNone->false|Somex->fixinarray_doi_opt_until_truegtletarray_fold_leftfacct=letgacc=functionNone->acc|Somex->faccxinA.fold_leftgacctletarray_fold_rightftacc=letgxacc=matchxwithNone->acc|Somex->fxaccinA.fold_rightgtaccletarray_on_first=letreciterftleni=ifi>=lenthenNoneelsematchA.gettiwith|None->iterftlen(i+1)|Somex->Some(fx)infunft->iterft(A.lengtht)0letarray_find_opt=letreciterpredtleni=ifi>=lenthenNoneelsematchpred(A.getti)with|true->Somei|false->iterpredtlen(i+1)infunpredt->iterpredt(A.lengtht)0letarray_shift_lefttpos=letlen=A.lengthtinifpos<=0||pos>=lenthenNoneelse(letante=pos-1inletvleft=A.gettanteinletvpos=A.gettposinA.settposvleft;A.settantevpos;Someante)letarray_shift_righttpos=letlen=A.lengthtinifpos<0||pos>=len-1thenNoneelse(letpost=pos+1inletvright=A.gettpostinletv=A.gettposinA.settposvright;A.settpostv;Somepost)(**/**)(** {2 The layers widget} *)(** A {!class-layers} widget can handle several widget trees,
allowing multiple layers of widgets. A layer is identified by its
position. Bottom layer has position [0]. A layer can have a root widget
associated (see the [pack] method). Widgets (and so, layers) are rendered
from bottom layer up to top layer, in this order.
*)classlayers?classes?name?props?wdata()=object(self)inherit[int]Container.container?classes?name?props?wdata()assuper(**/**)valmutablelayers=(A.create():Container.childoptionA.t)methodkind="layers"methodprivatefind_child_optpred=letres=refNoneinarray_do_until_true(func->matchpredcwith|true->res:=Somec;true|_->false)layers;!resmethodprivatefind_child_pos_optpred=letres=refNoneinarray_doi_until_true(funic->matchpredcwith|true->res:=Somei;true|_->false)layers;!resmethodprivatechild_by_widgetw=self#find_child_opt(func->c.widget#equalw)methodprivateremove_from_childrenw=matchself#widget_layerwwith|None->false|Somei->A.setlayersiNone;truemethodprivateiter_childrenf=A.iter(functionNone->()|Somec->fc)layersmethodprivatefold_children_rightfacc=array_fold_rightflayersaccmethodprivatefold_children_leftfacc=array_fold_leftfacclayersmethodprivateadd_to_children?poschild=letlayer=posinletlayer=matchlayerwith|Somei->i|None->matcharray_find_opt(functionNone->true|_->false)layerswith|None->A.lengthlayers|Somei->iin[%debug"%s#add_to_children layer=%d child=%s"self#melayerchild.widget#me];iflayer<0thenLog.err(funm->m"%s#pack: invalid layer %d"self#melayer)else(letlen=A.lengthlayersiniflayer<lenthenself#unpack_layerlayerelsefori=layerdowntolendoA.add_lastlayersNonedone;A.setlayerslayer(Somechild);)method!set_pp?delay?(propagate=false)v=[%debug"%s#set_p %s ~propagate:%b"self#me(Props.namep)propagate];super#set_p?delay~propagatepv;matchdelay,Props.transitionpwith|Some_,Some_->()|_->ifpropagatethenself#iter_children(func->c.widget#set_p~propagatepv)method!do_apply_theme~root~parentparent_pathrules=super#do_apply_theme~root~parentparent_pathrules;letpath=self#css_path~parent_path()inself#iter_children(func->c.widget#do_apply_theme~root~parent:theme_propspathrules);width_constraints<-None;height_constraints<-Nonemethod!wtree=letl=A.fold_right(funxacc->matchxwith|None->acc|Somec->c.Container.widget#wtree::acc)layers[]inWidget.N(self#coerce,l)method!baseline=matchself#children_widgetswith|[]->super#baseline|child::_->letb=child#baselineinletcg=child#geometryinb+cg.G.y+g_inner.ymethodwidth_constraints_=letmin=self#widget_min_widthinmatcharray_on_first(func->c.Container.widget#width_constraints)layerswith|None->Widget.size_constraintsmin|Somec->Widget.add_to_size_constraintscminmethodheight_constraints_=letmin=self#widget_min_heightinmatcharray_on_first(func->c.Container.widget#height_constraints)layerswith|None->Widget.size_constraintsmin|Somec->Widget.add_to_size_constraintscmin(**/**)(** {2 Properties} *)methodevent_direction=self#get_pevent_directionmethodset_event_direction=self#set_pevent_direction(** {2 Children widgets} *)(** Return the list of children widgets, from bottom layer
to top layer. Layers with no widget are present with
[None]. *)methodchildren=A.to_listlayers(** Return the list of children widgets, i.e. the root widget
of each layer, for layers which have one. *)methodchildren_widgets:Widget.widgetlist=A.fold_right(funxacc->matchxwith|None->acc|Somex->x.Container.widget::acc)layers[](** Return the layer of the given widget. The widget must be
the root widget of the layer.
If the widget is not found, [None] is returned.*)methodwidget_layer(w:Widget.widget)=matcharray_find_opt(functionSomec->c.Container.widget#equalw|None->false)layerswith|None->[%debug"%s#widget_layer: no widget %s"self#mew#me];None|x->x(** {2 Box.ng/unpacking} *)(** [#unpack_layer layer] removes the root widget of the given [layer]. *)methodunpack_layerlayer=iflayer>=0&&layer<A.lengthlayersthen(matchA.getlayerslayerwith|None->()|Somec->matchsuper#removec.widgetwith|false->()|true->ifc.widget#visiblethenself#need_resize)elseLog.err(funm->m"%s#unpack_layer: invalid layer %d"self#melayer)(** [#unpack_widget w] removes the root widget [w], setting root widget
for the corresponding layer to [None]. If the widget is not found, do
nothing.*)methodunpack_widgetw=matchsuper#removewwith|false->()|true->ifw#visiblethenself#need_resize(** [#pack w] packs widget [w] as root layer.
Optional arguments are:
{ul
{- [layer]: if specified, use [w] as new root widget for
this layer, eventually removing previous widget. If it is not specified,
pack the widget as root for the first layer without root widget,
eventually adding a layer to do so.}
{- [opacity] sets {!Props.opacity} property of [w].}
{- [data] associates the given value to [w] (in {!Container.child} structure).}
}
*)methodpack?layer?opacity?data(w:Widget.widget)=[%debug"%s#pack %s"self#mew#me];Option.iterw#set_opacityopacity;matchlayerwith|Somenwhenn<0->Log.err(funm->m"%s#pack: invalid layer %d"self#men)|_->matchsuper#add?pos:layer?datawwith|false->()|true->ifw#visiblethenself#need_resize(** {2 Operations on layers} *)(** [#move_layer layer target] moves layer according to [target], which
can have the following values:
{ul
{- [`Bottom]: moves layer at the bottom of all layers,}
{- [`Down n]: moves layer [n] positions down (or to the bottom if
[layer <= n]),}
{- [`Up n]: moves layer [n] positions up (or to the top if
[layer + n >= number of layers]),}
{- [`Top]: moves layer at the top of all layers.}
}
The method returns the new position of the layer, if it changed.
Remember that a layer is only identified by its position. So the
returned value is the same layer, with the same root widget.
*)methodmove_layerlayer(target:[`Top|`Bottom|`Upofint|`Downofint])=letlen=A.lengthlayersinmatchlayerwith|_whenlayer<0||layer>=len->Log.warn(funm->m"%s#move: invalid layer %d"self#melayer);None|_->letfinal=matchtargetwith|`Upn->letreciterip=ifi<nthenmatcharray_shift_rightlayerspwith|None->p|Somep->iter(i+1)pelsepiniter0layer|`Downn->letreciterip=ifi<nthenmatcharray_shift_leftlayerspwith|None->p|Somep->iter(i+1)pelsepiniter0layer|`Top->letreciterp=matcharray_shift_rightlayerspwith|None->p|Somep->iterpiniterlayer|`Bottom->letreciterp=matcharray_shift_leftlayerspwith|None->p|Somep->iterpiniterlayeriniffinal<>layerthen(self#need_resize;Somefinal)elseNone(**/**)method!privatefold_children_for_sdl_event_down=matchself#event_directionwith|Upward->self#fold_children_left|Downward->funfacc->letgxacc=faccxinself#fold_children_rightgaccmethod!privateon_sdl_event_down_stop_on_true=truemethod!grab_focus?(last=false)()=[%debug"%s#grab_focus ~last:%b"self#melast];ifself#visiblethenmatchself#get_pProps.can_focuswith|false->false|true->matchsuper#grab_focus~last()with|true->true|false->letreciter=function|[]->false|None::q->iterq|Somec::q->matchc.Container.widget#visible,c.widget#get_pProps.can_focuswith|true,true->(matchc.widget#grab_focus~last()with|false->iterq|true->true)|_->iterqiniter(iflastthenList.revself#childrenelseself#children)elsefalsemethodprivatechild_move_focus(w:Widget.widget)lastchildrenon_none=letreciterfound=function|None::q->iterfoundq|Somenext::qwhenfound->[%debug"%s#child_move_focus next.widget=%s, visible=%b"self#menext.Container.widget#menext.widget#visible];ifnext.Container.widget#visiblethenmatchnext.widget#grab_focus~last()with|false->iterfoundq|true->trueelseiterfoundq|Somec::q->letfound=Oid.equalc.widget#idw#idiniterfoundq|[]->on_none()initerfalsechildrenmethod!child_focus_next(w:Widget.widget)=self#child_move_focuswfalseself#children(fun()->self#focus_next)method!child_focus_prev(w:Widget.widget)=self#child_move_focuswtrue(List.revself#children)(fun()->self#focus_prev)method!set_geometrygeom=super#set_geometrygeom;letg_child={g_innerwithx=0;y=0}inself#iter_children(func->c.Container.widget#set_geometryg_child)method!leaf_widget_at~x~y=letx=x-g.x-g_inner.xinlety=y-g.y-g_inner.yinletfaccc=matchaccwith|None->c.Container.widget#leaf_widget_at~x~y|x->xinself#fold_children_leftfNone(**/**)endtypeWidget.widget_type+=Layersoflayers(** Convenient function to create a {!class-layers}.
See {!Widget.widget_arguments} for other arguments. *)letlayers?name?props?wdata?event_direction?pack()=letw=newlayers?name?props?wdata()inw#set_typ(Layersw);Option.iterw#set_event_directionevent_direction;Widget.may_pack?packw;w