123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367(*********************************************************************************)(* 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(**/**)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_parentrend~offset:(x,y)rg=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_renderg;falseinlet_id=self#connectWidget.Button_pressedon_buttoninlet_id=self#connectWidget.Button_releasedon_buttoninlet_id=self#connectWidget.Clickedself#on_clickedin()endtypeWidget.widget_type+=Buttonofbutton(** 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()inw#set_typ(Buttonw);Widget.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 {!Props.active} property.
Activating the widget toggles the state.
*)classtogglebutton?classes?name?props?wdata()=object(self)inheritbutton?classes?name?props?wdata()assupermethodactive=self#get_pProps.activemethodset_activex=self#set_pProps.activex(**/**)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_parentrend~offset:(x,y)rg=()endtypeWidget.widget_type+=Togglebuttonoftogglebutton(** 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()inw#set_typ(Togglebuttonw);Widget.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_pProps.activethenmatchelementswith|[]->Props.set_optpropsactive_widgetNone|w::_->self#set_activew(** Sets the active widget. *)methodset_active(wid:Widget.widget)=List.iter(fun(w:Widget.widget)->(* do not set wid's active to false to prevent looping *)ifnot(w#equalwid)thenw#set_pProps.activefalse)elements;wid#set_pProps.activetrue;self#set_pactive_widgetwid(* Gets the active widget, if any. *)methodactive_element=self#opt_pactive_widget(** Gets the {!Widget.wdata} associated to the active widget, if any. *)methodwdata=matchself#active_elementwith|None->None|Somew->w#wdataend(** Convenient function to create a {!class-group}. *)letgroup()=newgroup(** The checkbutton widget. *)classcheckbutton?classes?name?props?wdata()=lethbox=Box.hbox()inletindicator=Indicator.indicator~pack:(hbox#pack~hexpand:0)()inobject(self)inherittogglebutton?classes?name?props?wdata()assuper(**/**)methodkind="checkbutton"valmutablegroup=(None:groupoption)method!border_color=super#widget_border_color(**/**)(** The {!Indicator.class-indicator} widget. *)methodindicator=indicator(** {3 Properties} *)methodindicator_font=indicator#fontmethodset_indicator_font_desc=indicator#set_font_descmethodindicator_active_char=indicator#active_charmethodset_indicator_active_char=indicator#set_active_charmethodindicator_inactive_char=indicator#inactive_charmethodset_indicator_inactive_char=indicator#set_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#coercemethod!set_childw=letind_w=indicator#coerceinList.iter(funw->ifnot(w#equalind_w)thenhbox#unpackw)hbox#children_widgets;hbox#packwinitializersuper#set_childhbox#as_widget;indicator#connect_to_activeself#as_oendtypeWidget.widget_type+=Checkbuttonofcheckbutton(** 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()inw#set_typ(Checkbuttonw);Widget.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)classtypebutton_box=objectmethodbox:Box.boxmethodflex:Flex.flexmethodadd_space:Flex.spacemethodadd_text_button:?onclick:(unit->unit)->string->button*Text.labelmethodadd_button:?onclick:(unit->unit)->unit->buttonendletbutton_box?(classes=[])?name?props?wdata?pack():button_box=letbox=Box.vbox~classes:("button_box"::classes)?name?props?wdata?pack()inletflex=Flex.hflex~wrap:false~pack:box#pack()inletopt_connect_activate(b:button)=function|None->()|Somef->ignore(b#connectWidget.Activatedf)inletadd_text_button?onclicktext=let(b,l)=text_button~pack:flex#pack~text()inopt_connect_activatebonclick;(b,l)inletadd_button?onclick()=letb=button~pack:flex#pack()inopt_connect_activatebonclick;binobjectmethodbox=boxmethodflex=flexmethodadd_space=flex#pack_space()methodadd_text_button=add_text_buttonmethodadd_button=add_buttonend