123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561(*********************************************************************************)(* 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 *)(* *)(*********************************************************************************)(** Paned widgets. *)openMiscopenTsdlopenWidgetopenContainer(** A handle position, defined either as percentage or absolute position. *)typehandle_position=[`Percentoffloat|`Absoluteofint]lethandle_position_wrapper=letto_json?with_doc=function|`Percentv->`Floatv|`Absolutev->`Intvinletfrom_json?def=function|`Floatv->`Percent(max0.(min100.v))|`Intv->`Absolute(max0v)|json->Ocf.invalid_valuejsoninOcf.Wrapper.maketo_jsonfrom_json(**/**)moduleTHandle_positions=structtypet=handle_positionoptionlistletcompare=Stdlib.compareletwrapper=SomeOcf.Wrapper.(list(optionhandle_position_wrapper))lettransition=NoneendmodulePHandle_positions=Props.Add_prop_type(THandle_positions)(**/**)(** Property ["handle_positions"], to store the positions of the handles
in a {!class-paned} widget. *)lethandle_positions:handle_positionoptionlistProps.prop=PHandle_positions.mk_prop~after:[Props.Resize]~default:[]~inherited:false"handle_positions"letcss_handle_positions_prop=letstring_of_handle_position=function|`Percentf->Printf.sprintf"%f%%"f|`Absoluten->string_of_intninletto_string=Theme.(string_of_list(string_of_option_explicitstring_of_handle_position))inlethandle_position_parserctx=letopenAngstromin(Css.U.wsctx*>choice[(Css.Vp.numberctx>>=funf->Css.U.wsctx*>char'%'*>return(`Percentf));(Theme.Vp.intctx>>=funn->return(`Absoluten));])<?>"handle_position"inletparser=Theme.Vp.(list(explicit_opthandle_position_parser))inTheme.mk_propto_stringparser[]letcss_handle_positions=css_handle_positions_prophandle_positions(** How to define handle position when the user moves it: as percentage
or absolute value. This changes the way handle positions are updated
when the {!class-paned} widget is resized: with [`Percent], ratios between
children will be kept, but with [`Absolute] the handles will remain at
the same position. *)typeuser_handle_positionning=[`Percent|`Absolute]letuser_handle_positionnings=[`Percent;`Absolute]letstring_of_user_handle_positionning=function|`Percent->"percent"|`Absolute->"absolute"letuser_handle_positionning_of_string=Css.T.mk_of_string~case_sensitive:falsestring_of_user_handle_positionninguser_handle_positionningsletuser_handle_positionning_wrapper:user_handle_positionningOcf.wrapper=letto_json?with_docx=`String(string_of_user_handle_positionningx)inletfrom_json?def=function|(`Strings)asjson->(matchuser_handle_positionning_of_stringswith|None->Ocf.invalid_valuejson|Somex->x)|json->Ocf.invalid_valuejsoninOcf.Wrapper.maketo_jsonfrom_json(**/**)moduleTUser_handle_positionning=structtypet=user_handle_positionningletcompare=Stdlib.compareletwrapper=Someuser_handle_positionning_wrapperlettransition=NoneendmodulePUser_handle_positionning=Props.Add_prop_type(TUser_handle_positionning)(**/**)(** Property ["user_handle_positionning"]. *)letuser_handle_positionning:user_handle_positionningProps.prop=PUser_handle_positionning.mk_prop~default:`Percent~inherited:false"user_handle_positionning"letcss_user_handle_positioning_prop=Theme.keyword_propstring_of_user_handle_positionninguser_handle_positionning_of_string`Percentletcss_user_handle_positioning=css_user_handle_positioning_propuser_handle_positionning(**/**)letdefault_handle_props=letp=Props.empty()inProps.(setpwidth2;setpfg_colorColor.grey;);p(**/**)(** Property ["paned_handle_props"] to define appearance of
handles in {!class-paned} widget.
Default set {!Props.val-width} to [2] and {!Props.val-fg_color} to
{!Color.grey}.
*)lethandle_props=Props.props_prop~after:[Props.Resize]~default:default_handle_props~inherited:false"paned_handle_props"(** Property ["paned_user_set_handle_live_update"] defines whether
to update paned widget on each move of a handle by user ([true]),
or wait for the user to end moving the handle ([false]).
Default is [true]. It may be useful to set it to [false] when computation
of children sizes may take some time. *)letuser_set_handle_live_update=Props.bool_prop~after:[Props.Resize]~default:true~inherited:false"paned_user_set_handle_live_update"letcss_user_set_handle_live_update=Theme.bool_propuser_set_handle_live_update(*
let honor_child_min_size = Props.bool_prop ~after:[Props.Resize]
~default:true ~inherited:false "paned_honor_child_min_size"
*)(** Paned widget.
Contains children widget separated by handles. Horizontal (or vertical
if orientation is [Vertical]) space allocated to each child depends
on handle positions. The first handle position defines how to distribute
paned widget space between first and second children. The second handle
position indicate how to distribube the remaining space between the
second and third childreb, and so on.
The widget has class ["vertical"] or ["horizontal"] depending on orientation.
*)classpaned?classes?name?props?wdata()=object(self)inheritContainer.container_list?classes?name?props?wdata()assuperinheritWidget.orientedasoriented(**/**)methodkind="paned"(* coordinates of handles relative to g_inner *)valmutableg_handles=([]:G.tlist)valmutablehandle_cursor=let>c=Sdl.(create_system_cursorSystem_cursor.size_ns)incvalmutablecursor_on_handle=falsevalmutablestate_machine=Misc.empty_state_machinemethod!privatethemable_props=handle_props::super#themable_props(**/**)(** {2 Properties} *)methodhandle_props=self#get_phandle_propsmethodset_handle_props=self#set_phandle_propsmethodhandle_positions=self#get_phandle_positionsmethodset_handle_positions=self#set_phandle_positionsmethoduser_handle_positionning=self#get_puser_handle_positionningmethodset_user_handle_positionning=self#set_puser_handle_positionningmethoduser_set_handle_live_update=self#get_puser_set_handle_live_updatemethodset_user_set_handle_live_update=self#set_puser_set_handle_live_update(*
method honor_child_min_size = self#get_p honor_child_min_size
method set_honor_child_min_size = self#set_p honor_child_min_size
*)(** {2 Children} *)(** [o#children_widgets] returns the list of children widget of [o]. *)methodchildren_widgets=List.map(func->c.widget)self#children(** [o#reorder_child w pos] moves child widget [w] to new position [pos]
(if possible). *)methodreorder_childwpos=super#reorder_childwpos(**/**)methodprivatewidth_constraints_=letmin=self#widget_min_widthinletinter_padding=Props.(getself#handle_propswidth)inmatchself#orientationwith|Horizontal->Box.width_constraints_horizontalchildren~inter_paddingmin|Vertical->Box.width_constraints_verticalchildren~inter_paddingminmethodprivateheight_constraints_=letmin=self#widget_min_heightinletinter_padding=Props.(getself#handle_propswidth)inmatchself#orientationwith|Horizontal->Box.height_constraints_horizontalchildren~inter_paddingmin|Vertical->Box.height_constraints_verticalchildren~inter_paddingminmethodprivatemin_handles_width=letvchildren=self#visible_childreninmatchself#orientationwith|Horizontal->letw=Props.(getself#handle_propswidth)in(max0(List.lengthvchildren-1))*w|Vertical->0methodprivatemin_handles_height=letvchildren=self#visible_childreninmatchself#orientationwith|Horizontal->letw=Props.(getself#handle_propswidth)in(max0(List.lengthvchildren-1))*w|Vertical->0methodprivateset_geometry_horizontal=lethandle_w=Props.(getself#handle_propswidth)inletreciteraccvchildrenhandle_posx=matchvchildrenwith|[]->List.revacc|[w]->w#set_geometry{G.x;y=0;w=g_inner.w-x;h=g_inner.h};List.revacc|w1::qw->letremain_w=max0(g_inner.w-x-handle_w)inlet(target_w,qpos)=matchhandle_poswith|[]asq|None::q->remain_w/2,q|(Some(`Absolutep))::q->minremain_wp,q|(Some(`Percentp))::q->truncate(floatremain_w*.(p/.100.)),qinletg1=letw=letwc=w1#width_constraintsinletw=maxwc.mintarget_winmatchwc.max_abswith|None->w|Somemaxw->minwmaxwin{G.x;y=0;w;h=g_inner.h}inw1#set_geometryg1;letgh={G.x=g1.x+g1.w;y=0;w=handle_w;h=g_inner.h}initer(gh::acc)qwqpos(gh.x+gh.w)ing_handles<-iter[]self#visible_childrenself#handle_positions0methodprivateset_geometry_vertical=lethandle_h=Props.(getself#handle_propswidth)inletreciteraccvchildrenhandle_posy=matchvchildrenwith|[]->List.revacc|[w]->w#set_geometry{G.x=0;y;w=g_inner.w;h=g_inner.h-y};List.revacc|w1::qw->letremain_h=max0(g_inner.h-y-handle_h)inlet(target_h,qpos)=matchhandle_poswith|[]asq|None::q->remain_h/2,q|(Some(`Absolutep))::q->minremain_hp,q|(Some(`Percentp))::q->truncate(floatremain_h*.(p/.100.)),qinletg1=leth=lethc=w1#height_constraintsinleth=maxhc.mintarget_hinmatchhc.max_abswith|None->h|Somemaxh->minhmaxhin{G.x=0;y;w=g_inner.w;h}inw1#set_geometryg1;letgh={G.x=0;y=g1.y+g1.h;w=g_inner.w;h=handle_h}initer(gh::acc)qwqpos(gh.y+gh.h)ing_handles<-iter[]self#visible_childrenself#handle_positions0method!set_geometrygeom=super#set_geometrygeom;[%debug"%s#set_geometry g=%a"self#meG.ppg];(matchself#orientationwith|Horizontal->self#set_geometry_horizontal|Vertical->self#set_geometry_vertical);self#need_renderg(**/**)(** [o#pack w] adds widget [w] to [o]. Optional parameter
[pos] indicates a position to insert [w]; default is to
append [w] to children.
Optional parameter [data] associates the given value to [w].
*)methodpack?pos?dataw=[%debug"%s#add %s"self#mew#me];matchsuper#add?pos?datawwith|false->()|true->ifw#visiblethenself#need_resize(** [o#unpack w] removes child widget [w] from [o]. *)methodunpack(w:Widget.widget)=matchsuper#removewwith|false->()|true->ifw#visiblethenself#need_resize(** [o#unpack_all ~destroy] removes all children from [o]. [destroy]
indicates whether to call [#destroy] on children after removing. *)methodunpack_all~destroy=matchself#children_widgetswith|[]->()|l->letold_nr=ignore_need_resizeinself#ignore_need_resize;List.iter(funw->self#unpackw;ifdestroythenw#destroy)l;ifnotold_nrthen(self#handle_need_resize;self#need_resize)(**/**)(* coordinates relative to g_inner *)methodprivatehandle_from_coords~x~y=letrecitern=function|[]->None|gh::q->letgh=matchself#orientationwith|Horizontal->G.enlarge~w:2gh|Vertical->G.enlarge~h:2ghinifG.inside~x~yghthenSomenelseiter(n+1)qiniter0g_handlesmethodprivateuser_set_handle_posn~x~y=letreciteraccprev_boundi(lgh:G.tlist)lpos=matchlghwith|[]->List.revacc|gh::qghwheni=n->letbound=matchself#orientationwith|Horizontal->max0((minxg_inner.w)-prev_bound)|Vertical->max0((minyg_inner.h)-prev_bound)inletbound=matchself#user_handle_positionningwith|`Absolute->`Absolutebound|`Percent->letlimit=matchself#orientationwith|Horizontal->g_inner.w|Vertical->g_inner.hin`Percent((floatbound/.float(limit-prev_bound))*.100.)inletlpos=matchlposwith|[]->[Somebound]|_::lpos->(Somebound)::lposin(List.revacc@lpos)|gh::qgh->letprev_bound=matchself#orientationwith|Horizontal->gh.x+gh.w+1|Vertical->gh.y+gh.h+1inlet(acc,lpos)=matchlposwith|[]->None::acc,[]|p::q->(p::acc),qiniteraccprev_bound(i+1)qghlposinletnew_pos=iter[]00g_handlesself#handle_positionsin[%debug"%s#user_set_handle_pos n=%d x=%d y=%d handle_positions=%a"self#menxy(Props.pp_prophandle_positions)new_pos];self#set_handle_positionsnew_posmethod!on_sdl_event_down~oldposposev=ifself#sensitivethenmatchstate_machine.fposevwith|false->super#on_sdl_event_down~oldposposev|true->trueelsefalsemethodon_mouse_leave=(matchstate_machine.state()with|`Moving_handle_->state_machine.set_state`Base|_->());super#on_mouse_leavemethodprivaterestore_cursor=Sdl.set_cursorself#top_widget#cursormethodstate_on_eventstateposev=matchstate,pos,Sdl.Event.(enum(getevtyp))with|`Base,Some(x,y),`Mouse_motion->((* change cursor if needed *)(*Log.warn (fun m -> m "%s#state_on_event mouse_motion g=%a x=%d y=%d"
self#me G.pp g x y);*)let(x,y)=self#to_g_inner_coords~x~yinmatchself#handle_from_coords~x~y,cursor_on_handlewith|None,false->None|None,true->self#restore_cursor;cursor_on_handle<-false;None|Some_,true->None|Some_,false->cursor_on_handle<-true;Sdl.set_cursor(Somehandle_cursor);None)|`Moving_handlen,Some(x,y),`Mouse_motion->ifG.inside~x~ygthen(let(x,y)=self#to_g_inner_coords~x~yinifself#user_set_handle_live_updatethenself#user_set_handle_posn~x~y;Some(`Moving_handlen,true))else(cursor_on_handle<-false;self#restore_cursor;Some(`Base,false))|`Base,Some(x,y),`Mouse_button_down->letbutton=Sdl.Event.(getevmouse_button_button)inifbutton=1thenlet(x,y)=self#to_g_inner_coords~x~yinmatchself#handle_from_coords~x~ywith|None->None|Somen->Some(`Moving_handlen,true)elseNone|`Moving_handlen,Some(x,y),`Mouse_button_up->let(x,y)=self#to_g_inner_coords~x~yinself#user_set_handle_posn~x~y;Some(`Base,false)|(`Base|`Moving_handle_),_,_->Nonemethod!render_me(rend:Sdl.renderer)~offset:(x,y)(rg:G.t)=letoff_x=g.x+g_inner.xinsuper#render_merend~offset:(x,y)rg;letoff_y=g.y+g_inner.yinletoffset=(x+off_x,y+off_y)inletrg=G.translate~x:(-off_x)~y:(-off_y)rginList.iter(self#render_handlerend~offsetrg)g_handlesmethodrender_handlerend~offset:(x,y)rggh=matchG.interrgghwith|None->()|Someclip->letclip=G.translate~x~yclipinletfrend=letgh=G.translate~x~yghinletcol=Props.(getself#handle_propsfg_color)inRender.fill_rectrend(Somegh)colinRender.with_cliprend(G.to_rectclip)fmethod!destroy=super#destroy;Sdl.free_cursorhandle_cursorinitializerstate_machine<-Misc.mk_state_machine`Baseself#state_on_event;ignore(self#connect(Object.Prop_changedProps.orientation)(fun~prev~now->Sdl.free_cursorhandle_cursor;let>c=Sdl.(create_system_cursorSystem_cursor.(matchself#orientationwith|Horizontal->size_we|Vertical->size_ns))inhandle_cursor<-c))endtypeWidget.widget_type+=Panedofpanedletpanedorientation?classes?name?user_set_handle_live_update?props?wdata?pack()=letw=newpaned?classes?name?props?wdata()inw#set_typ(Panedw);w#set_orientationorientation;Option.iterw#set_user_set_handle_live_updateuser_set_handle_live_update;Widget.may_pack?packw;wlethpaned=panedHorizontalletvpaned=panedVertical