123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377(*********************************************************************************)(* 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 *)(* *)(*********************************************************************************)(** Range widget.
This widget allows selecting a float value between two bounds.
*)openMiscopenWidgetopenTsdl(** {2 Properties}
All properties are not inherited. *)(** Property ["range_range"] to represent minimum and maximum values.
Default is [0.]..[1.].
*)letrange=Props.float_pair_prop~after:[Render]~default:(0.,1.)~inherited:false"range_range"(** Property ["range_value"] to store the currnt value of the range. *)letvalue=Props.float_prop~after:[Resize]~inherited:false"range_value"(** Property ["range_step"] to specify a step. Used when some left/right or
up/down arrows are pressed. Default is [0.1]. *)letstep=Props.float_prop~default:0.1~inherited:false"range_step"(** Property ["range_bigstep"] to specify a step. Used when some page up/down
keys are pressed. Default is [1.]. *)letbigstep=Props.float_prop~default:1.~inherited:false"range_bigstep"(** Property ["pole_size"] defines, in pixels, vertical or horizontal size
of range poles. Default is [15]. *)letpole_size=Props.int_prop~default:15~after:[Resize]~inherited:false"pole_size"letcss_pole_size=Theme.int_proppole_size(** Property ["pole_width"] defines, in pixels, the width of range poles.
Default is [4]. *)letpole_width=Props.int_prop~default:4~after:[Resize]~inherited:false"pole_width"letcss_pole_width=Theme.int_proppole_width(** Property ["pole_color_low"] defines the color of the lower bound pole.*)letpole_color_low=Props.color_prop~after:[Render]~inherited:false"pole_color_low"letcss_pole_color_low=Theme.color_proppole_color_low(** Property ["pole_color_high"] defines the color of the upper bound pole.*)letpole_color_high=Props.color_prop~after:[Render]~inherited:false"pole_color_high"letcss_pole_color_high=Theme.color_proppole_color_high(** Property ["range_line_width"] defines the width, in pixels,
of the line between the two poles. Default is [2].*)letline_width=Props.int_prop~default:2~after:[Resize]~inherited:false"range_line_width"letcss_line_width=Theme.int_propline_width(** Property ["range_cursor_bg_color"] defines the background color of range cursor. *)letcursor_bg_color=Props.color_prop~after:[Render]~inherited:false"range_cursor_bg_color"letcss_cursor_bg_color=Theme.color_propcursor_bg_color(** Property ["range_cursor_border_color"] defines the border color of range cursor. *)letcursor_border_color=Props.color_prop~after:[Render]~inherited:false"range_cursor_border_color"letcss_cursor_border_color=Theme.color_propcursor_border_color(** {2 Range widget} *)(** The range widget. *)classrange?classes?name?props?wdata()=object(self)inheritWidget.widget?classes?name?props?wdata()assuper(**/**)methodkind="range"valmutablecursor_rect=G.zerovalmutablestate_machine=Misc.empty_state_machine(**/**)(** {2 Properties} *)methodvalue=self#get_pvalue(** Setting a value out or bounds will only log an error message. *)methodset_valuev=let(start,stop)=self#rangeinifv<start||v>stopthenLog.err(funm->m"%s: value %f out of range (%f, %f)"self#mevstartstop)else(self#set_pvaluev;self#update_cursor_rect)(** Normalized value, i.e. between [0.] and [1.]. *)methodnorm_value=let(lo,hi)=self#rangeinletd=hi-.loin((self#value-.lo)/.d)methodrange=self#get_prangemethodset_ranger=let(start,stop)=rinifstart>stopthenLog.err(funm->m"%s: invalid range (%f, %f)"self#mestartstop)else(self#set_pranger;letv=self#valueinifv<startthenself#set_valuestartelseifv>stopthenself#set_valuestop)methodorientation=self#get_pProps.orientationmethodset_orientation=self#set_pProps.orientationmethodline_width=self#get_pline_widthmethodset_line_width=self#set_pline_widthmethodpole_width=self#get_ppole_widthmethodset_pole_width=self#set_ppole_widthmethodpole_size=self#get_ppole_sizemethodset_pole_size=self#set_ppole_sizemethodcursor_width=self#get_pProps.cursor_widthmethodset_cursor_width=self#set_pProps.cursor_widthmethodcursor_bg_color=self#get_pcursor_bg_colormethodset_cursor_bg_color=self#set_pcursor_bg_colormethodcursor_border_color=self#get_pcursor_border_colormethodset_cursor_border_color=self#set_pcursor_border_colormethodstep=self#get_pstepmethodset_step=self#set_pstepmethodbigstep=self#get_pbigstepmethodset_bigstep=self#set_pbigstep(**/**)method!privatemin_width_=super#min_width_+matchself#orientationwith|Horizontal->2*self#pole_width+1|Vertical->maxself#pole_sizeself#line_widthmethod!privatemin_height_=super#min_height_+matchself#orientationwith|Vertical->2*self#pole_width+1|Horizontal->maxself#pole_sizeself#line_widthmethodupdate_cursor_rect=letr=letcw=self#cursor_widthinmatchself#orientationwith|Props.Vertical->letx=0inlety=truncate((1.-.self#norm_value)*.float(g_inner.h-cw))inletw=g_inner.winleth=cwin{G.x;y;w;h}|Props.Horizontal->letx=truncate(self#norm_value*.float(g_inner.w-cw))inlety=0inletw=cwinleth=g_inner.hin{G.x;y;w;h}in[%debug"%s#update_cursor_rect => %a"self#meG.ppr];cursor_rect<-r;self#invalidate_texture;self#need_render~layer:(self#get_pProps.layer)gmethod!set_geometrygeom=super#set_geometrygeom;self#update_cursor_rectmethodrender_cursorrendt=Texture.fill_rectrendt(Somecursor_rect)self#cursor_bg_color;Texture.draw_rect_rrendtcursor_rectself#cursor_border_colormethod!render_me~layerrend~offsetgeom=super#render_with_prepare~layerrend~offsetgeom;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: rendering on texture"self#me];(* When orientation=Horizontal:
p00 p10
|(low pole) (high pole)|
p1=========p0===========p2
| |
p01 p11
When orientation=Vertical:
p10-p1-p11 (high pole)
|
|
p0
|
|
p00-p2-p01 (low pole)
*)letx0=g_inner.w/2andy0=g_inner.h/2inletlw=self#line_widthinletpole_width=self#pole_widthinletx1,y1,w,h,xlo,ylo,wlo,hlo,xhi,yhi,whi,hhi=matchself#orientationwith|Horizontal->letx1=0andy1=y0-(lw/2)inletw=x0*2andh=lwinletxlo=x1andylo=0inletwlo=pole_widthandhlo=2*y0inletxhi=x0*2-pole_widthandyhi=0inletwhi=pole_widthandhhi=2*y0inx1,y1,w,h,xlo,ylo,wlo,hlo,xhi,yhi,whi,hhi|Vertical->letx1=x0-(lw/2)andy1=0inletw=lwandh=y0*2inletxlo=0andylo=g_inner.h-pole_widthinletwlo=2*x0andhlo=pole_widthinletxhi=0andyhi=y1inletwhi=2*x0andhhi=pole_widthinx1,y1,w,h,xlo,ylo,wlo,hlo,xhi,yhi,whi,hhiinlet()=letr=G.create~x:x1~y:y1~w~hinTexture.fill_rectrendt(Somer)self#fg_color_nowinlet()=letc=matchself#opt_ppole_color_lowwith|None->self#fg_color_now|Somec->cinletr=G.create~x:xlo~y:ylo~w:wlo~h:hloinTexture.fill_rectrendt(Somer)cinlet()=letc=matchself#opt_ppole_color_highwith|None->self#fg_color_now|Somec->cinletr=G.create~x:xhi~y:yhi~w:whi~h:hhiin[%debug"%s: g_inner=%a, r(high pole)=%a"self#meG.ppg_innerG.ppr];Texture.fill_rectrendt(Somer)cinself#render_cursorrendt;Somet)elseNonemethod!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_leavemethoduser_set_cursor_pos~x~y=letv=let(lo,hi)=self#rangeinletratio=matchself#orientationwith|Vertical->1.-.(floaty/.(float(g_inner.h-self#cursor_width)))|Horizontal->floatx/.(float(g_inner.w-self#cursor_width))inmaxlo(minhi(lo+.ratio*.(hi-.lo)))inself#set_valuevmethodstate_on_eventstateposev=matchstate,pos,Sdl.Event.(enum(getevtyp))with|`Base,Some(x,y),`Mouse_motion->None|`Moving_handle,Some(x,y),`Mouse_motion->ifG.inside~x~ygthen(let(x,y)=self#to_g_inner_coords~x~yinself#user_set_cursor_pos~x~y;None)else(Some(`Base,false))|`Base,Some(x,y),`Mouse_button_down->letbutton=Sdl.Event.(getevmouse_button_button)inifbutton=1&&G.inside~x~ygthenlet_=self#grab_focus()inlet(x,y)=self#to_g_inner_coords~x~yinifG.inside~x~ycursor_rectthenSome(`Moving_handle,true)else(self#user_set_cursor_pos~x~y;Some(`Base,true))elseNone|`Moving_handle,Some(x,y),`Mouse_button_up->Some(`Base,false)|(`Base|`Moving_handle),_,_->Nonemethod!on_key_downposevkeykeymod=matchkeywith|kwhenk=Sdl.K.home->self#set_value(fstself#range);true|kwhenk=Sdl.K.kend->self#set_value(sndself#range);true|kwhenk=Sdl.K.pageup->self#set_value(min(sndself#range)(self#value+.self#bigstep));true|kwhenk=Sdl.K.pagedown->self#set_value(max(fstself#range)(self#value-.self#bigstep));true|kwhenk=Sdl.K.up||k=Sdl.K.right->self#set_value(min(sndself#range)(self#value+.self#step));true|kwhenk=Sdl.K.down||k=Sdl.K.left->self#set_value(max(fstself#range)(self#value-.self#step));true|_->super#on_key_downposevkeykeymodinitializerself#set_value(fstself#range);state_machine<-Misc.mk_state_machine`Baseself#state_on_event;ignore(self#connect(Object.Prop_changedProps.has_focus)(fun~prev~now->self#invalidate_texture))end(** Convenient function to create a {!class-range}.
See {!Widget.widget_arguments} for optional arguments [classes],
[name], [props] and [pack]. Other arguments will set range properties.*)letrange?classes?name?props?wdata?orientation?range?step?bigstep?value?pack()=letw=newrange?classes?name?props?wdata()inOption.iterw#set_orientationorientation;Option.iterw#set_rangerange;Option.iterw#set_stepstep;Option.iterw#set_bigstepbigstep;Option.iterw#set_valuevalue;Widget.may_pack?packw;w