123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399(*********************************************************************************)(* 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 *)(* *)(*********************************************************************************)(** Buttons. *)openMiscopenWidgetopenTsdl(** Property used to indicate whether a toggle or option
button is activate.*)letactive=Props.(bool_prop~after:[Render]~default:false~inherited:false"active")(**/**)letactive_widget=Widget.widget_prop~inherited:false"active_widget"(**/**)(** {2 Simple buttons} *)(** Simple button.*)classbutton?classes?name?props?wdata()=object(self)inheritBin.bin?classes?name?props?wdata()assuper(**/**)methodkind="button"method!set_childw=super#set_childw;w#set_handle_hoveringtruemethod!render_me_parent~layerrend~offset:(x,y)rg=iflayer=self#get_pProps.layerthen(matchbutton_pressedwith|Some1->letrg=G.translate~x~yrginRender.fill_rectrend(Somerg)(self#get_pProps.click_mask)|_->())method!on_key_downposeventkeymods=[%debug"%s#on_key_down"self#me];matchkeywith|kwhenk=Sdl.K.space->self#activate;true|_->super#on_key_downposeventkeymods(**/**)(** Triggers the {!Widget.extension-Activated} event on the button. *)methodactivate=[%debug"%s activated"self#me];self#trigger_unit_eventWidget.Activated()(**/**)methodprivateon_clickedev=ifev.Widget.button=1then(self#activate;true)elsefalseinitializerProps.(setpropsfocusabletrue);self#set_handle_hoveringtrue;leton_button_=self#need_render~layer:(self#get_pProps.layer)g;falseinlet_id=self#connectWidget.Button_pressedon_buttoninlet_id=self#connectWidget.Button_releasedon_buttoninlet_id=self#connectWidget.Clickedself#on_clickedin()end(** Convenient function to create a {!class-button}.
See {!Widget.widget_arguments} for arguments. *)letbutton?classes?name?props?wdata?pack()=letw=newbutton?classes?name?props?wdata()inWidget.may_pack?packw#coerce;w(** Convenient function to create a {!class-button} with
a {!Text.class-label} as child.
[text] optional argument is passed to {!Text.val-label}.
[label_class] is passed as [?class_] argument when creating
label.
See {!Widget.widget_arguments} for other arguments. *)lettext_button?classes?label_classes?name?props?wdata?text?pack()=letlabel=Text.label?classes:label_classes?text()inletb=button?classes?name?props?wdata?pack()inb#set_childlabel#coerce;(b,label)(** {2 Toggle buttons} *)(** A toggle button. State is represented by the {!active} property.
Activating the widget toggles the state.
*)classtogglebutton?classes?name?props?wdata()=object(self)inheritbutton?classes?name?props?wdata()assupermethodactive=self#get_pactivemethodset_activex=self#set_pactivex(**/**)methodkind="togglebutton"methodactivate=self#set_active(notself#active);super#activatemethodprivatewidget_border_color=super#border_colormethod!border_color=letc=super#border_colorinifself#activethencelseProps.{top=c.bottom;right=c.left;bottom=c.top;left=c.right}methodrender_me_parent~layerrend~offset:(x,y)rg=()end(** Convenient function to create a {!class-togglebutton}.
Initial state can be specifier with the [active] argument
(default is false).
See {!Widget.widget_arguments} for other arguments. *)lettogglebutton?classes?name?props?wdata?active?pack()=letw=newtogglebutton?classes?name?props?wdata()inWidget.may_pack?packw#coerce;Option.iterw#set_activeactive;w(** Convenient function to create a {!class-togglebutton} with
a {!Text.class-label} as child.
Initial state can be specifier with the [active] argument
(default is false).
[text] optional argument is passed to {!Text.val-label}.
[label_class] is passed as [?class_] argument when creating
label.
See {!Widget.widget_arguments} for other arguments. *)lettext_togglebutton?classes?label_classes?name?props?wdata?active?text?pack()=letlabel=Text.label?classes:label_classes?text()inletb=togglebutton?classes?name?props?wdata?active?pack()inb#set_childlabel#coerce;(b,label)(** {2 Check and radio buttons} *)(** A group is used to share a state among several checkbuttons,
so they act as radio buttons (only one can be active at the
same time). *)classgroup=object(self)inheritObject.o()assuper(**/**)valmutableelements=([]:Widget.widgetlist)(**/**)(** Adds a widget to the group. The widget becomes active
if it is the first in the group. *)methodadd(w:Widget.widget)=elements<-w::elements;matchelementswith|[_]->self#set_activew|_->()(** Removes a widget to the group. If the widget was active,
the first of the remaining widgets become active. *)methodremove(w:Widget.widget)=letid=w#idinelements<-List.filter(funw->not(Oid.equalidw#id))elements;ifw#get_pactivethenmatchelementswith|[]->Props.set_optpropsactive_widgetNone|w::_->self#set_activew(** Sets the active widget. *)methodset_active(w:Widget.widget)=List.iter(fun(w:Widget.widget)->w#set_pactivefalse)elements;w#set_pactivetrue;self#set_pactive_widgetw(* Gets the active widget, if any. *)methodactive_element=self#get_pactive_widget(** Gets the {!Widget.wdata} associated to the active widget, if any. *)methodwdata=self#active_element#wdataend(** Convenient function to create a {!class-group}. *)letgroup()=newgroup(** The following properties are used to tune the appearance
of checkbuttons: a font and active and inactive characters. *)letcheck_indicator_font=Props.font_desc_prop~after:[Props.Resize]~inherited:false~default:(Font.font_desc~size:14"DejaVu Sans")"check_indicator_font"letcss_check_indicator_font=Theme.font_desc_propcheck_indicator_fontletcheck_indicator_active_char=Props.uchar_prop~after:[Props.Resize]~inherited:false~default:(Uchar.of_int9724)"check_indicator_active_char"letcss_check_indicator_active_char=Theme.uchar_propcheck_indicator_active_charletcheck_indicator_inactive_char=Props.uchar_prop~after:[Props.Resize]~inherited:false~default:(Uchar.of_int9723)"check_indicator_inactive_char"letcss_check_indicator_inactive_char=Theme.uchar_propcheck_indicator_inactive_char(** The checkbutton widget. *)classcheckbutton?classes?name?props?wdata()=object(self)inherittogglebutton?classes?name?props?wdata()assuper(**/**)methodkind="checkbutton"valmutablegroup=(None:groupoption)method!border_color=super#widget_border_color(**/**)(** {3 Properties} *)methodindicator_font=self#get_pcheck_indicator_fontmethodset_indicator_font=self#set_pcheck_indicator_fontmethodindicator_active_char=self#get_pcheck_indicator_active_charmethodset_indicator_active_char=self#set_pcheck_indicator_active_charmethodindicator_inactive_char=self#get_pcheck_indicator_inactive_charmethodset_indicator_inactive_char=self#set_pcheck_indicator_inactive_charmethodprivateindicator_char=ifself#activethenself#indicator_active_charelseself#indicator_inactive_char(** {3 The group} *)methodgroup=groupmethodset_groupg=(matchgroupwith|None->()|Someg->g#removeself#coerce);group<-Someg;g#addself#coerce;ifself#activetheng#set_activeself#coerceelse()(**/**)method!set_activeb=matchgroup,bwith|None,_->super#set_activeb|Some_,false->()|Someg,true->g#set_activeself#coercevalmutableg_indicator=G.zeromethodupdate_g_indicator=letf=Font.getself#indicator_fontinletdesc=Font.font_descentfinlet>(w,h)=Font.size_utf8f(Utf8.string_of_ucharself#indicator_char)inletgi=G.{x=0;y=max0((g_inner.h-(h-desc))/2);w;h}in(*Log.warn (fun m -> m "%s#update_g_indicator => %a" self#me G.pp gi);*)g_indicator<-gimethodspace_for_child=letg_ind=g_indicatorinletip=self#get_pPack.inter_paddinginletx=g_ind.x+g_ind.w+ipin{G.x;y=0;w=g_inner.w-x;h=g_inner.h;}method!compute_child_geometry_=self#space_for_childmethod!set_geometrygeom=super#set_geometrygeom;self#update_g_indicatormethod!privatemin_width_=self#update_g_indicator;letip=self#get_pPack.inter_paddinginletw=super#widget_min_width_+g_indicator.x+g_indicator.w+ip+self#child_min_widthin(*Log.warn (fun m -> m "%s#min_width_=%d (g_indicator=%a)" self#me w G.pp g_indicator);*)wmethod!privatemin_height_=self#update_g_indicator;super#widget_min_height_+maxg_indicator.hself#child_min_heightmethodrender_me_parent~layerrend~offset:(x,y)rg=[%debug"#render_me_parent rg=%a, g=%a, g_inner=%a"G.pprgG.ppgG.ppg_inner];iflayer=self#get_pProps.layerthen(letg_ind=G.translate~x:(g.x+g_inner.x)~y:(g.y+g_inner.y)g_indicatorin[%debug"#render_me_parent rg=%a, (translated)g_ind=%a"G.pprgG.ppg_ind];matchG.interg_indrgwith|None->()|Someclip->letclip=G.translate~x~yclipin[%debug"clip rect: %a"G.ppclip];letfrend=letg_ind=G.translate~x~yg_indinletglyph=Uchar.to_intself#indicator_charinletfont=Font.getself#indicator_fontinlet>surf=Font.render_glyph_blendedfontglyph(Color.to_sdl_colorself#fg_color_now)inlet>t=Sdl.create_texture_from_surfacerendsurfinTexture.finalise_sdl_texturet;letsrc=G.to_rect{g_indwithx=0;y=0}inletdst=G.to_rectg_indinlet>()=Sdl.render_copyrend~src~dsttin()inRender.with_cliprend(G.to_rectclip)f)end(** Convenient function to create a {!class-checkbutton}.
Initial state can be specifier with the [active] argument
(default is false).
See {!Widget.widget_arguments} for other arguments. *)letcheckbutton?classes?name?props?wdata?group?active?pack()=letw=newcheckbutton?classes?name?props?wdata()inWidget.may_pack?packw#coerce;Option.iterw#set_groupgroup;Option.iterw#set_activeactive;w(** Convenient function to create a {!class-checkbutton} acting
as a radio button (with class ["radiobutton"]).
Initial state can be specifier with the [active] argument
(default is false).
[group] can be used to set the group the radio button belongs to.
See {!Widget.widget_arguments} for other arguments. *)letradiobutton?(classes=[])?name?props?wdata?group?active?pack()=letclasses="radio"::classesincheckbutton~classes?name?props?wdata?group?active?pack()(** Convenient function to create a {!class-checkbutton} with
a {!Text.class-label} as child.
Initial state can be specifier with the [active] argument
(default is false).
[text] optional argument is passed to {!Text.val-label}.
[label_classes] is passed as [?classes] argument when creating
label.
See {!Widget.widget_arguments} for other arguments. *)lettext_checkbutton?classes?label_classes?name?props?wdata?group?active?text?pack()=letlabel=Text.label?classes:label_classes?text()inletb=checkbutton?classes?name?props?wdata?group?active?pack()inb#set_childlabel#coerce;(b,label)(** Convenient function to create a {!class-checkbutton} acting
as a radio button (with class ["radiobutton"])
with a {!Text.class-label} as child.
Initial state can be specifier with the [active] argument
(default is false).
[group] can be used to set the group the radio button belongs to.
[text] optional argument is passed to {!Text.val-label}.
[label_classes] is passed as [?classes] argument when creating
label.
See {!Widget.widget_arguments} for other arguments. *)lettext_radiobutton?classes?label_classes?name?props?wdata?group?active?text?pack()=letlabel=Text.label?classes:label_classes?text()inletb=radiobutton?classes?name?props?wdata?group?active?pack()inb#set_childlabel#coerce;(b,label)