123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386(*********************************************************************************)(* 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 *)(* *)(*********************************************************************************)(** Labelled frame widget. *)openMiscopenWidgetopenTsdl(** Property ["frame_border_width"] to specify the width of the
frame's border. *)letborder_width=Props.int_prop~after:[Resize]~default:1"frame_border_width"letcss_border_width=Theme.int_propborder_width(** The frame widget. Optional argument [label] can be used
to pass the widget used in label position. Any widget can be
used as label. *)classframe?classes?name?props?wdata?label()=object(self)inheritBin.bin?classes?name?props?wdata()assuper(**/**)methodkind="frame"valmutablelabel=(label:Widget.widgetoption)method!set_pp?delay?(propagate=false)v=[%debug"%s#set_p ~propagate:%b"self#mepropagate];super#set_p?delay~propagatepv;matchdelay,Props.transitionpwith|Some_,Some_->()|_->ifpropagatethenmatchlabelwith|None->()|Somew->w#set_p~propagatepvelse()method!child_reparentedw=matchlabelwith|Somecwhenc#equalw->self#set_labelNone|_->super#child_reparentedwmethod!do_apply_theme~root~parentparent_pathrules=super#do_apply_theme~root~parentparent_pathrules;letpath=self#css_path~parent_path()inOption.iter(funw->w#do_apply_theme~root~parent:theme_propspathrules)label(**/**)(** {2 Properties} *)methodframe_border_width=self#get_pborder_widthmethodset_frame_border_width=self#set_pborder_width(**/**)method!wtree=letl=matchlabelwith|None->[]|Somew->[w#wtree]inletl=matchchildwith|None->l|Somew->l@[w#wtree]inWidget.N(self#coerce,l)method!render_child~layerrenderer~offset~g_none~g_child=matchchildwith|None->()|Some_->super#render_child~layerrenderer~offset~g_none~g_childmethod!render_me_parent~layerrend~offsetgeom=super#render_with_prepare~layerrend~offsetgeom;self#render_label~layerrend~offsetgeommethodrender_label~layerrend~offset:(x,y)geom=matchlabelwith|None->()|Somew->letoffset=(x+g.x+g_inner.x,y+g.y+g_inner.x)inletgeom=G.translate~x:(-g.x-g_inner.x)~y:(-g.y-g_inner.x)geominw#render~layerrend~offsetgeommethodlabel_min_width=matchlabelwith|None->0|Somew->w#min_widthmethodlabel_min_height=matchlabelwith|None->0|Somew->w#min_heightmethodlabel_margin=matchlabelwith|None->Props.trbl__0|Somew->w#marginmethodprivatetop_padding=letp=self#paddinginletlabel_h=self#label_min_heightinmaxp.top(label_h/2)method!privatemin_width_=letp=self#paddinginletcm=self#child_margininletcw=self#child_min_widthinletbw=self#frame_border_widthin(* use super#min_width but remove child_min_width *)super#min_width_-cw+bw+(max(maxp.leftcm.left+cw-cm.left-cm.right+maxp.rightcm.right)(p.left+self#label_min_width+p.right))+bwmethod!privatemin_height_=letp=self#paddinginletbw=self#frame_border_widthinletlh=self#label_min_heightinletlm=self#label_margininletcm=self#child_margininletch=self#child_min_heightin(* use super#min_height but remove child_min_height *)super#min_height_-ch+(max(max0(lm.top-p.top)+lh-lm.top-lm.bottom+maxlm.bottomcm.top)(bw+maxp.topcm.top))+ch-cm.top-cm.bottom+maxp.bottomcm.bottom+bwmethod!compute_child_geometryw=letglabel=matchlabelwith|None->G.zero|Somew->w#geometryinletlm=self#label_margininletcm=w#margininletbw=self#frame_border_widthinletp=self#paddinginletx=bw+maxcm.leftp.leftinlety=max(bw+maxcm.topp.top)(max0(lm.top-p.top)+glabel.h+maxlm.bottomcm.top)inletgc={G.x;y;w=g_inner.w-x-(bw+maxp.rightcm.right);h=g_inner.h-y-(bw+maxp.bottomcm.bottom);}in[%debug"%s#compute_child_geometry: g=%a, g_inner=%a, gc=%a"self#meG.ppgG.ppg_innerG.ppgc];gcmethodprivateset_label_parent=matchlabelwith|None->()|Somew->w#add_class"frame_label";w#set_parent?with_rend:self#with_renderer(Someself#coerce)(**/**)(** {2 Label} *)(** Returns the label widget, if any. *)methodlabel=label(** [f#set_label w] sets widget [w] as label, replacing the
previous one if present. *)methodset_labelw=matchlabel,wwith|Somel,Somewwhenl#equalw#as_widget->Log.warn(funm->m"%s is already label for %s"w#meself#me)|_->Option.iter(funl->l#rem_class"frame_label";l#set_parent?with_rend:NoneNone)label;label<-w;let()=matchlabelwith|None->()|Somew->Option.iter(funp->p#child_reparentedw)w#parent;self#set_label_parentinself#need_resize(**/**)method!set_geometrygeom=(matchlabelwith|None->()|Somec->letold_geo=c#geometryinletw=self#label_min_widthinleth=self#label_min_heightinletm=self#label_margininletp=self#paddinginletbw=self#frame_border_widthinletgeo={G.x=bw+p.left+m.left;y=max0(m.top-p.top);w=w-m.left-m.right;h=h-m.top-m.bottom}in[%debug"%s#set_geometry label#geometry=>%a"self#meG.ppgeo];c#set_geometrygeo;(* if label's geometry changed, we destroy our texture
or else the frame border will not be redrawn *)ifold_geo<>geothenself#destroy_texture);super#set_geometrygeom;method!prepare~layerrendgeom=iflayer=self#get_pProps.layerthen(matchself#texturerendwith|None->[%debug"%s#prepare: no texture"self#me];None|Some(`Existt)->Somet|Some(`Newt)->[%debug"%s#prepare: drawing on my texture"self#me];letp=self#paddinginletbw=self#frame_border_widthinletlabel_m=self#label_marginin(*let cm = self#child_margin in*)letglabel=matchlabelwith|None->G.zero|Somew->letge=w#geometryin{G.x=p.left+bw;y=ge.y;w=ge.w+label_m.left+label_m.right;h=ge.h(*+ max 0 (label_m.top - p.top) + max label_m.bottom cm.top*);}inlet()=letx=0inlety=max0(glabel.y+(glabel.h-bw)/2)inletw=g_inner.winleth=max0(g_inner.h-y)inletr=ref{G.x;y;w;h}inleti=refbwinwhile!i>0do[%debug"%s#prepare draw_rect %a glabel=%a"self#meG.pp!rG.ppglabel];Texture.draw_rect_rrendt!rself#fg_color_now;decri;r:=G.enlarge~w:(-1)~h:(-1)!r;donein(matchlabelwith|Some_->Texture.fill_rectrendt(Someglabel)self#bg_color_now|None->());Somet)elseNonemethod!on_sdl_event_down~oldpospose=ifself#sensitivethenletb=matchlabelwith|None->false|Somew->letchild_pos=Option.mapself#to_child_coordsposinletchild_oldpos=Option.mapself#to_child_coordsoldposinw#on_sdl_event_down~oldpos:child_oldposchild_poseinmatchbwith|true->true|false->super#on_sdl_event_down~oldposposeelsefalsemethod!is_leaf_widget=falsemethod!leaf_widget_at~x~y=matchG.insideg~x~ywith|false->None|true->matchmatchlabelwith|None->None|Somew->let(x,y)=self#to_child_coords(x,y)inw#leaf_widget_at~x~ywith|None->super#leaf_widget_at~x~y|(Some_)asx->xmethod!next_widget?inside~looppredw=matchw,labelwith|None,None->super#next_widget?inside~looppredNone|None,Somel->(matchl#next_widget?inside~looppredNonewith|None->super#next_widget?inside~looppredNone|x->x)|Somew,Somelwhenw#equall->l#next_widget?inside~looppredNone|Some_,_->super#next_widget?inside~looppredNonemethod!prev_widget?inside~looppredw=matchw,child,label,parent,insidewith|None,None,None,_,Someiwhenself#equali#coerce->None|None,None,None,None,_->None|None,None,None,Somep,_->p#prev_widget?inside~looppred(Someself#coerce)|None,Somec,None,_,_->super#prev_widget?inside~looppredNone|None,None,Somel,_,Someiwhenself#equali#coerce->(matchl#prev_widget?inside~looppredNonewith|None->ifloopthenself#prev_widget?inside~looppredNoneelseNone|x->x)|None,None,Somel,None,_->(matchl#prev_widget?inside~looppredNonewith|None->None|x->x)|None,None,Somel,Somep,_->(matchl#prev_widget?inside~looppredNonewith|None->p#prev_widget?inside~looppred(Someself#coerce)|x->x)|Somew,Somec,Somel,_,Someiwhenself#equali&&w#equalc->(matchl#prev_widget?inside~looppredNonewith|None->ifloopthenself#prev_widget?inside~looppredNoneelseNone|x->x)|Somew,Somec,Somel,None,_whenw#equalc->(matchl#prev_widget?inside~looppredNonewith|None->None|x->x)|Somew,Somec,Somel,Somep,_whenw#equalc->(matchl#prev_widget?inside~looppredNonewith|None->p#prev_widget?inside~looppred(Someself#coerce)|x->x)|_,_,_,_,Someiwhenself#equali->ifloopthenself#prev_widget?inside~looppredNoneelseNone|_,_,_,None,_->None|_,_,_,Somep,_->p#prev_widget?inside~looppred(Someself#coerce)initializerself#set_label_parent;(* TODO: handle properties which should trigger invalidating the texture *)ignore(self#connect(Object.Prop_changedProps.is_focus)(fun~prev~now->[%debug"%s: is_focus -> %b, invalidating texture"self#menow];self#invalidate_texture))end(** Convenient function to create a {!class-frame}.
Optional arguments [label] can be used to add the widget to
be placed in label position.
See {!Widget.widget_arguments} for other arguments. *)letframe?name?props?wdata?label?pack()=letw=newframe?name?props?wdata?label()inWidget.may_pack?packw;w