12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016(* we use Sdlevent plus some additions *)(* Warning, SDL is not thread-safe, so we implement some mutexes and
don't use wait_event *)(* Doc wiki:
NOTE: You should not expect to be able to create a window, render, or
receive events on any thread other than the main one *)(* It is not clear to me if a thread is authorized to *send* an event
to SDL *)(* WARNING: just loading this module will execute some Sdl functions (bad?).
Thus, it will initialize SDL *)openTsdlopenB_utilsmoduleE=Sdl.EventmoduleTime=B_timemoduleTimeout=B_timeoutmoduleVar=B_varopenResult(* We initialize SDL with only the events subsystem *)let()=Sdl.(initInit.nothing)|>go;printddebug_warning"SDL initialized";leta,b,c=Sdl.get_version()inSdl.log"Using SDL %u.%u.%u"abc;Sdl.(init_sub_systemInit.events)|>go;printddebug_event"SDL Events initialized"typet=Sdl.event_typeletevent_names:(t,string)Hashtbl.t=Hashtbl.create20letname_list=[E.finger_down,"finger_down";E.finger_motion,"finger_motion";E.finger_up,"finger_up";E.key_down,"key_down";E.key_up,"key_up";E.mouse_button_down,"mouse_button_down";E.mouse_button_up,"mouse_button_up";E.mouse_motion,"mouse_motion";E.mouse_wheel,"mouse_wheel";E.sys_wm_event,"sys_wm_event";E.text_editing,"text_editing";E.text_input,"text_input";E.display_event,"display_event";E.window_event,"window_event"]let()=List.iter(fun(e,n)->Hashtbl.addevent_namesen)name_list(* this will be set by the main loop in Bogue *)letmain_tread_id=ref(-1)letnew_event_typename=matchSdl.register_event()with|None->failwith"Cannot register event."|Somet->(Hashtbl.addevent_namestname;printddebug_event"Register new event type:%s (Ox%x)"namet;t)letcreate_eventt=lete=E.create()inE.(setetypt);eletcreate_window_eventw_id=lete=create_eventE.window_eventinE.(setewindow_event_idw_id);e(* we create new user types. The first one should be the predefined
E.user_event *)letuser_type=new_event_type"user"let()=assert(user_type=E.user_event)(* 32768*)(* Failing here probably means that Bogue has been loaded twice without quitting
SDL *)letuser_event=E.user_eventletstop=new_event_type"stop"letstopped=new_event_type"stopped"letmouse_enter=new_event_type"mouse_enter"letmouse_leave=new_event_type"mouse_leave"(* The mouse_leave and mouse_enter events are sent when the layout under the
mouse changes, and if the mouse button is *not* clicked* -- because in this
case, the clicked widget should keep the focus until the button is
released. However, in the current implementation, if the button is down, the
mouse_leave event will be sent when the mouse leaves the Bogue window. See
[Main.check_mouse_motion]. *)letredraw=new_event_type"redraw"(* TODO: select only a particular canvas *)(* let refresh = new_event_type () *)letmouse_at_rest=new_event_type"mouse_at_rest"letstartup=new_event_type"startup"(* The var_changed event can be send to notify that some widget made a change to
a global variable. Not used yet. *)letvar_changed=new_event_type"var_changed"(* The update event can be used to trigger some actions when a widget is
updated, even if it doesn't get mouse focus. It is filtered early in
b_main.ml and not sent to the mouse_focus. At this point it is not clear
whether we need both var_changed and update *)letupdate=new_event_type"update"letsync_action=new_event_type"sync_action"letkeyboard_focus=new_event_type"keyboard_focus"letmouse_focus=new_event_type"mouse_focus"letremove_focus=new_event_type"remove_focus"(* let remove_layout = new_event_type "remove_layout" *)letdestroy_window=new_event_type"destroy_window"letadd_window=new_event_type"add_window"letnot_used=new_event_type"not_used"(* Some aliases. Beware that in case of finger events, the OS or SDL will most
likely trigger the corresponding mouse event as well, so it's not necessary
to react to both. *)letbuttons_down=E.[mouse_button_down;finger_down]letbuttons_up=E.[mouse_button_up;finger_up]letpointer_motion=E.[mouse_motion;finger_motion](* This event is used by the main loop, and sometimes we need to send it to
other threads. In this case we create a new event for the main loop, using
renew_my_event below. *)letmy_event=ref(E.create())letrenew_my_event()=lete=create_eventnot_usedin(* this should be innocuous *)my_event:=eletof_eventev=E.(getevtyp)typesdl_event=Sdl.Event.enumtypebogue_event=[`Bogue_startup|`Bogue_stop|`Bogue_stopped|`Bogue_mouse_at_rest|`Bogue_mouse_enter|`Bogue_mouse_leave|`Bogue_var_changed|`Bogue_keyboard_focus|`Bogue_mouse_focus|`Bogue_remove_focus|`Bogue_destroy_window|`Bogue_update|`Bogue_sync_action|`Bogue_redraw|`Bogue_keymap_changed(* SDL event, which was missing in Tsdl. *)|`Bogue_add_window|`SDL_POLLSENTINEL]letgeneralize_sdl_eventev=(ev:sdl_event:>[>sdl_event|bogue_event])letgeneralize_bogue_eventev=(ev:bogue_event:>[>sdl_event|bogue_event])letevent_kindev:[>sdl_event|bogue_event]=matchE.(enum(getevtyp))with(* It would be nice that Tsdl puts the event enum type as refinable variant
type with [>...]. Then we could add our own variant tags. *)|`Unknownx->beginmatchxwith(* TODO association list or Imap like tsdl.ml *)|iwheni=startup->`Bogue_startup|iwheni=stop->`Bogue_stop|iwheni=stopped->`Bogue_stopped|iwheni=mouse_at_rest->`Bogue_mouse_at_rest|iwheni=mouse_enter->`Bogue_mouse_enter|iwheni=mouse_leave->`Bogue_mouse_leave|iwheni=var_changed->`Bogue_var_changed|iwheni=keyboard_focus->`Bogue_keyboard_focus|iwheni=mouse_focus->`Bogue_mouse_focus|iwheni=remove_focus->`Bogue_remove_focus|iwheni=destroy_window->`Bogue_destroy_window|iwheni=update->`Bogue_update|iwheni=redraw->`Bogue_redraw|iwheni=sync_action->`Bogue_sync_action|iwheni=add_window->`Bogue_add_window|iwheni=0x304->`Bogue_keymap_changed(* keymap_changed was forgotten, but corrected in recent Tsdl.
https://github.com/dbuenzli/tsdl/issues/76#event-6708707941 See also:
https://github.com/libsdl-org/SDL/issues/5520 *)|iwheni=0x7f00->printd(debug_event+debug_error)"SDL lost an SDL_POLLSENTINEL! Maybe you used to many threads?";`SDL_POLLSENTINEL|_->printddebug_event"UNKNOWN EVENT=0x%x"x;`Unknownxend|e->generalize_sdl_evente(* Query replace regexp (default `\([a-z_]+\)\1): `\([a-z]+\)`\1 -> "\1" *)letwindow_event_name=function|`Close->"Close"|`Enter->"Enter"|`Exposed->"Exposed"|`Focus_gained->"focus_gained"|`Focus_lost->"Focus_lost"|`Hidden->"Hidden"|`Hit_test->"Hit_test"|`Leave->"Leave"|`Maximized->"Maximized"|`Minimized->"Minimized"|`Moved->"Moved"|`Resized->"Resized"|`Restored->"Restored"|`Shown->"Shown"|`Size_changed->"Size_changed"|`Take_focus->"Take_focus"|`Unknown_->"Unknown"(* Some dumb code to duplicate an event. Probably much easier directly in
C... *)typefield=|ButtonofSdl.button_stateE.field|FingerofSdl.finger_idE.field|FloatoffloatE.field|GestureofSdl.gesture_idE.field|HatofSdl.Hat.tE.field|IntofintE.field|Int16ofSdl.int16E.field|Int32ofint32E.field|JoystickofSdl.joystick_idE.field|KeycodeofSdl.keycodeE.field|KeymodofSdl.keymodE.field|ScancodeofSdl.scancodeE.field|StringofstringE.field|TimestampofSdl.uint32E.field|TouchofSdl.touch_idE.field|TypeofSdl.event_typeE.field|Uint32ofSdl.uint32E.field|Uint8ofSdl.uint8E.field|WindowofE.window_event_idE.fieldletcommon_fields=letopenEin[Typetyp;Timestamptimestamp]lettouch_finger_fields=letopenEin[Touchtouch_finger_touch_id;Fingertouch_finger_finger_id;Floattouch_finger_x;Floattouch_finger_y;Floattouch_finger_dx;Floattouch_finger_dy;Floattouch_finger_pressure;]letcontroller_button_fields=letopenEin[Joystickcontroller_button_which;Uint8controller_button_button;Buttoncontroller_button_state]letdollar_gesture_fields=letopenEin[Touchdollar_gesture_touch_id;Gesturedollar_gesture_gesture_id;Intdollar_gesture_num_fingers;Floatdollar_gesture_error;Floatdollar_gesture_x;Floatdollar_gesture_y]letjoy_button_fields=letopenEin[Joystickjoy_button_which;Uint8joy_button_button;Buttonjoy_button_state]letjoy_device_fields=letopenEin[Joystickjoy_device_which]letkeyboard_fields=letopenEin[Intkeyboard_window_id;Buttonkeyboard_state;Intkeyboard_repeat;Scancodekeyboard_scancode;Keycodekeyboard_keycode;Keymodkeyboard_keymod]letmouse_button_fields=letopenEin[Intmouse_button_window_id;Uint32mouse_button_which;Uint8mouse_button_button;Buttonmouse_button_state;Uint8mouse_button_clicks;Intmouse_button_x;Intmouse_button_y]letmouse_motion_fields=letopenEin[Intmouse_motion_window_id;Uint32mouse_motion_which;Uint32mouse_motion_state;Intmouse_motion_x;Intmouse_motion_y;Intmouse_motion_xrel;Intmouse_motion_yrel]letuser_fields=letopenEin[Intuser_window_id;Intuser_code]letspecial_fields_per_type=letopenEin[controller_axis_motion,[Joystickcontroller_axis_which;Uint8controller_axis_axis;Int16controller_axis_value];controller_button_down,controller_button_fields;controller_button_up,controller_button_fields;controller_device_added,[Joystickcontroller_device_which];controller_device_remapped,(* idem *)[Joystickcontroller_device_which];controller_device_removed,(* idem *)[Joystickcontroller_device_which];dollar_gesture,dollar_gesture_fields;dollar_record,dollar_gesture_fields;(* warning drop_file_file cannot be copied *)finger_down,touch_finger_fields;finger_motion,touch_finger_fields;finger_up,touch_finger_fields;joy_axis_motion,[Joystickjoy_axis_which;Uint8joy_axis_axis;Int16joy_axis_value];joy_ball_motion,[Joystickjoy_ball_which;Uint8joy_ball_ball;Intjoy_ball_xrel;Intjoy_ball_yrel];joy_button_down,joy_button_fields;joy_button_up,joy_button_fields;joy_device_added,joy_device_fields;joy_device_removed,joy_device_fields;joy_hat_motion,[Joystickjoy_hat_which;Uint8joy_hat_hat;Hatjoy_hat_value];key_down,keyboard_fields;key_up,keyboard_fields;mouse_button_down,mouse_button_fields;mouse_button_up,mouse_button_fields;mouse_motion,mouse_motion_fields;mouse_wheel,[Intmouse_wheel_window_id;Uint32mouse_wheel_which;Intmouse_wheel_x;Intmouse_wheel_y];multi_gesture,[Touchmulti_gesture_touch_id;Floatmulti_gesture_dtheta;Floatmulti_gesture_ddist;Floatmulti_gesture_x;Floatmulti_gesture_y;Intmulti_gesture_num_fingers];text_editing,[Inttext_editing_window_id;Stringtext_editing_text;Inttext_editing_start;Inttext_editing_length];text_input,[Inttext_input_window_id;Stringtext_input_text];user_event,user_fields;window_event,[Intwindow_window_id;Windowwindow_event_id;Int32window_data1;Int32window_data2];]letcopy_fielde1e2field=letx=E.gete1fieldinE.sete2fieldxletcopy_fielde1e2field=matchfieldwith|Buttonf->copy_fielde1e2f|Fingerf->copy_fielde1e2f|Floatf->copy_fielde1e2f|Gesturef->copy_fielde1e2f|Hatf->copy_fielde1e2f|Intf->copy_fielde1e2f|Int16f->copy_fielde1e2f|Int32f->copy_fielde1e2f|Joystickf->copy_fielde1e2f|Keycodef->copy_fielde1e2f|Keymodf->copy_fielde1e2f|Scancodef->copy_fielde1e2f|Stringf->copy_fielde1e2f|Timestampf->copy_fielde1e2f|Touchf->copy_fielde1e2f|Typef->copy_fielde1e2f|Uint32f->copy_fielde1e2f|Uint8f->copy_fielde1e2f|Windowf->copy_fielde1e2fletcopy_fieldse1e2fields=List.iter(copy_fielde1e2)fields(* pourquoi est-ce qu'on ne ferait pas juste un push/pop event ? *)letcopy_evente=lete2=E.create()incopy_fieldsee2common_fields;lett=E.(getetyp)inletfields=ift>E.user_event(* all event types greater than user_event are user created events *)thenuser_fieldselsetryList.assoctspecial_fields_per_typewithNot_found->[]incopy_fieldsee2fields;e2(* val enum : Tsdl.Sdl.event_type -> *)(* [ `App_did_enter_background *)(* | `App_did_enter_foreground *)(* | `App_low_memory *)(* | `App_terminating *)(* | `App_will_enter_background *)(* | `App_will_enter_foreground *)(* | `Clipboard_update *)(* | `Controller_axis_motion *)(* | `Controller_button_down *)(* | `Controller_button_up *)(* | `Controller_device_added *)(* | `Controller_device_remapped *)(* | `Controller_device_removed *)(* | `Dollar_gesture *)(* | `Dollar_record *)(* | `Drop_file *)(* | `Finger_down *)(* | `Finger_motion *)(* | `Finger_up *)(* | `Joy_axis_motion *)(* | `Joy_ball_motion *)(* | `Joy_button_down *)(* | `Joy_button_up *)(* | `Joy_device_added *)(* | `Joy_device_removed *)(* | `Joy_hat_motion *)(* | `Key_down *)(* | `Key_up *)(* | `Mouse_button_down *)(* | `Mouse_button_up *)(* | `Mouse_motion *)(* | `Mouse_wheel *)(* | `Multi_gesture *)(* | `Quit *)(* | `Sys_wm_event *)(* | `Text_editing *)(* | `Text_input *)(* | `Unknown *)(* | `User_event *)(* | `Window_event ] *)(* USER 0 = ask for refresh *)(* let user id = *)(* let () = match Sdl.register_event () with (\* useful ?? *\) *)(* | None -> failwith "Cannot register event. Bla?" *)(* | Some t -> print_debug "Event type:%d (%d)" t E.user_event in *)(* let open Sdl.Event in *)(* let e = create () in *)(* set e typ user_event; *)(* set e user_code id; *)(* e;; *)(* let user0 = user 0;; *)(* let add ev = *)(* print_debug "Add USER 0"; *)(* if not (go (Sdl.push_event ev)) then print_debug "Warning: Event filtered";; *)(* let add_user x = if x = 0 then add user0;; *)lettext_eventev=matchevent_kindevwith|`Clipboard_update|`Key_down|`Key_up|`Text_editing|`Text_input->true|_->falseletflushkind=Sdl.flush_eventkindletsprint_evev=letopenPrintfinlett=E.(getevtyp)inletname=trysprintf" (%s)"(Hashtbl.findevent_namest)withNot_found->""insprintf"0x%x%s"tnameletpush_eventev=printddebug_event"Pushing event %s"(sprint_evev);ifnot(go(Sdl.push_eventev))thenprintddebug_event"Warning: Event filtered"(* There seems to be a problem with [Sdl.poll_event None] on some platforms/SDL
versions: on the macOS VirtualBox with SDL 2.0.18, it returns false
positives. See https://discourse.libsdl.org/t/pollevent-inconsistency/34358/3
and the fix in
https://github.com/libsdl-org/SDL/commit/dca281e810263f1fbf9420c2988932b7700be1d4
Meanwhile, we avoid using [Sdl.poll_event None]. *)let_has_no_event_old()=not(Sdl.poll_eventNone)(* SDL_PeepEvents is currently not bound by Tsdl unfortunately. *)(* We don't use this anymore, because for some reason (SDL 2.0.10) when the
event is 0x702 SDL_FINGERMOTION, [push_event] will push instead an event of
type 0x802 SDL_MULTIGESTURE, (even if we filter this out using
Sdl.set_event_state E.multi_gesture Sdl.disable !!). This can easily cause
an accumulation of thousands of events in the main event_loop... !! *)let_has_no_event_old()=lete=E.create()inifSdl.poll_event(Somee)thenbeginprintddebug_event"Event remaining: %s"(sprint_eve);push_evente;falseendelsetruelethas_no_event()=Sdl.has_eventsE.first_eventE.last_event|>not(** get the list of all events, and remove them from the queue *)(* the first of the list is the oldest, ie the one to be popped at the next
wait_event *)letget_all_events()=letreclooplist=lete=E.create()inifSdl.poll_event(Somee)thenloop(e::list)elseList.revlistinloop[](* Leave only those events that satisfy the filter test and return the others *)letfilter_eventsfilter=letfilter=if!debugthenfunev->letresult=filterevinprintddebug_event"Filter on event #%u = %b"E.(getevtyp)result;resultelsefilterinletlist=get_all_events()inletkeep,remove=List.partitionfilterlistinList.iterpush_eventkeep;remove(* Remove all events of this kind and return the last one (=most recent).
Warning: treating the last event without deleting other events changes the
logical order of emitted events. *)letget_lastkind=ifSdl.has_eventkindthenletremove=filter_events(funev->E.(getevtyp)<>kind)inifremove=[]thenfailwith"[Trigger.get_last]: list should not be empty."elseSome(List.hd(List.revremove))elseNone(* TODO optimize by using a get_all_events which does not do List.rev *)(* Leave at most n events in the queue, can also filter *)(* this is brutal: we pop all events, and then push back only n *)(* if there is a filter, only those events that satisfy the filter are left in
the queue *)letflush_but_n?filtern=letall_events=get_all_events()inletrecloopilist=ifi<=0then()elsematchlistwith|[]->()|e::rest->matchfilterwith|Somef->iffethen(push_evente;loop(i-1)rest)elseloopirest|None->(push_evente;loop(i-1)rest)inprintddebug_event"Number of events:%u"(List.lengthall_events);loopnall_eventsletflush_all()=ignore(get_all_events())letflush_nn=lete=E.create()inletrecloopi=ifi>0&&Sdl.poll_event(Somee)thenloop(i-1)inloopn(* some aliases *)lettext_input=E.text_inputletkey_down=E.key_downletkey_up=E.key_up(* we save here the id of the room corresponding to the event *)letroom_id=E.user_codeletwidget_id=E.user_code(* TODO: it would be more efficient to store directly the room, rather that
storing the id, and then painfully search the room corresponding to id...
(not to mention that this can cause serious problems when the event lives
longer than the room...) But this implies creating a new event type, not
using the Sdl.Event directly. *)(* Some remarks about the mouse_enter/leave events, in relation to
animation. (To test this, use example 28 where there is a Select list
combined with a scrolling.) In the functions push_mouse_enter/leave, we
create a new event each time because it IS possible that several mouse_enter
events get in the queue, eg. in the following scenario:
- trigger mouse_enter
- trigger another event (eg. mouse_motion)
- new main loop iteration
- the mouse_motion event is treated
- the mouse leaves the widget and enters another one: we trigger a new mouse_enter
(it is easy to do this in case of animation, because then there is a "long"
FPS wait in the loop... change this?)
- end of the loop. Now we have two mouse_enter events in the queue.
What makes this possible is
1. only one event is treated per iteration (2022: not true anymore)
2. there might be quite a long delay in the loop in case of animation
This behaviour has a drawback: during an animation, mouse_enter/leave events
may lag behing real time.
In fact, the design of 2 is questionable: one could design the main loop
another way: the animation does not use a FPS delay, but instead would react
to a repetitive event that would be triggered at 60FPS. Or move the code of
Timer.fps inside the main loop without blocking.
*)(* push a event and store the id in the user_code *)letpush_from_idev_typeid=lete=create_eventev_typeinE.(seteuser_codeid);push_evente(* use this when the mouse enters a new widget. id is the id of the room
containing the widget. *)letpush_mouse_enter=push_from_idmouse_enter(* see push_mouse_enter *)letpush_mouse_leave=push_from_idmouse_leave(* use this to request a redraw of the widget, id is the widget_id *)(* in the current implementation, it asks the whole Window to refresh *)letpush_redraw=push_from_idredraw(* use this when the layout claims keyboard focus *)(* the id is of the layout, which should contain a Widget. *)letpush_keyboard_focus=push_from_idkeyboard_focus(* use this when the layout claims mouse focus *)(* the id is of the layout, which should contain a Widget. *)letpush_mouse_focus=push_from_idmouse_focus(* when removing a layout you should tell the board to remove it from its
current focus, if applicable. *)letpush_remove_focus=push_from_idremove_focus(* use this when a Tvar (or dynvar, not used) has changed *)(* at this time, the significance of the id argument is not specified. (not used
yet). It could be a widget_id, room_id, or dynavar_id... TODO create a
specific event for each of them *)letpush_var_changed=push_from_idvar_changed(* the widget_id is stored in the update event *)letpush_update=push_from_idupdate(* id of layout to add as a new window *)letpush_add_window=push_from_idadd_window(* The layout id is stored but not used. Only one push is necessary
currently. *)(* let push_remove_layout id = *)(* if not (Sdl.has_event remove_layout) *)(* then push_from_id remove_layout id *)letpush_destroy_window~window_idid=lete=create_eventdestroy_windowinE.(seteuser_codeid);E.(seteuser_window_idwindow_id);push_eventeletpush_closeid=lete=create_eventE.window_eventinE.(setewindow_window_idid);E.(setewindow_event_idwindow_event_close);push_evente(* send the `Quit event *)letpush_quit()=push_from_idE.quit0letget_update_wide=ifE.(getetyp)<>updatethenfailwith"Event should be an update event"elseE.(geteuser_code)(* use this to inform that there is a new Sync.action to execute *)(* the event doesn't contain any special data, so we always use the same *)letpush_action=letaction_event=create_eventsync_actioninfun()->push_eventaction_event(** tell if the current thread should exit. This should be called within a
widget action. We communicate via the event to decide if the thread should
exit *)letshould_exitev=(* printd debug_thread "should exit ? event type = %d" (E.(get ev typ)); *)E.(getevtyp)=stopletwill_exitev=E.(setevtyp)stopped(** a delay that can be stopped via the event *)letnice_delayevsec=lett=sec+.Unix.gettimeofday()inletrecloop()=if(Unix.gettimeofday()>=t)||(should_exitev)then()else(Thread.delay0.003;loop())inloop()(** wait until the value is observed, or timeout is elapsed. Return true is the
value was observed *)letwait_value?timeoutevvarvalue=lett0=Unix.gettimeofday()inwhilenot(Var.getvar=value)&&(default(map_optiontimeout(funt->Unix.gettimeofday()<t+.t0))true)&¬(should_exitev)doThread.delay0.003;done;Var.getvar=value(** wait until condition returns true *)letwait_for?timeout?evcond=letev=defaultev!my_eventinlett0=Unix.gettimeofday()inwhilenot(cond())&&(default(map_optiontimeout(funt->Unix.gettimeofday()<t+.t0))true)&¬(should_exitev)doThread.delay0.01;done(* WARNING: in the current implementation of widget.ml, all events that can be
sent to a widget are likely to be *mutated* by a connection/thread to the
widget. Hence it is not safe to use global event variables. We create a new
event each time we need it, or at least each time we send it to the
Widget. *)(* EDIT: now we duplicate the event before sending it to a thread, so this
should be safer, see widget.ml/add_action *)letfull_click_magic=255(* A full_click event is actually a mouse_button_up but whose clicks fields is
set to full_click_magic. This way we can use the x,y fields, which would be
difficult with a user_event. *)letset_full_clicke=E.(setemouse_button_clicks)full_click_magiclethas_full_clickev=E.(getevtyp=mouse_button_up)&&E.(getevmouse_button_clicks)=full_click_magicletstartup_event()=create_eventstartupletis_mouse_at_rest=reffalse(* Get mouse position in OS pixels *)letmouse_pos()=snd(Sdl.get_mouse_state())(* check if mouse didn't move for a while *)(* TODO use get_touch_finger *)letcheck_mouse_rest=lett=refNoneinleton_mouse_idle()=push_event@@create_eventmouse_at_restinletstart_timer()=t:=Some(mouse_pos(),Timeout.add1000on_mouse_idle)infun()->match!twith|None->start_timer()|Some(pos0,timeout)->letp=mouse_pos()inifp<>pos0(* we have moved *)thenbeginTimeout.canceltimeout;start_timer()endletno_timeout()=-1letstart_noevent_fps,poll_noevent_fps=Time.make_fps()letwait_event_timeout=letmajor,minor,patch=Sdl.get_version()inif(major,minor,patch)>=(2,0,16)thenSdl.wait_event_timeoutelsefunev_->Sdl.poll_eventev(* Wait for next event. Returns the SAME event structure e (modified) *)letrecwait_event?(action=no_timeout)e=check_mouse_rest();lettimeout=action()inpoll_noevent_fps100;lethas_event=wait_event_timeout(Somee)timeoutinifhas_eventtheneelsewait_event~actioneletmm_pressedev=Int32.logandE.(getevmouse_motion_state)(Sdl.Button.lmask)<>0l(* Maybe all the *_window_id fields in fact are the same int? *)letwindow_idev=matchevent_kindevwith|`Key_down|`Key_up->E.(getevkeyboard_window_id)|`Mouse_button_down|`Mouse_button_up->E.(getevmouse_button_window_id)|`Mouse_motion->E.(getevmouse_motion_window_id)|`Mouse_wheel->E.(getevmouse_wheel_window_id)|`Text_editing->E.(getevtext_editing_window_id)|`Text_input->E.(getevtext_input_window_id)|`User_event->E.(getevuser_window_id)|`Bogue_destroy_window->E.(getevuser_window_id)|`Window_event->E.(getevwindow_window_id)|_->(* TODO mouse_enter/leave *)printddebug_event"Warning! this event has no window id; fallback on mouse_focus";matchSdl.get_mouse_focus()with|Somew->Sdl.get_window_idw|None->printddebug_event"Hmm, no mouse_focus either, trying keyboard_focus";matchSdl.get_keyboard_focus()with|Somew->Sdl.get_window_idw|None->printddebug_event"Ah. no keyboard_focus. trying any window";letrecloopid=ifid>=1024then(printddebug_event"No window found ! giving 0 and crossing fingers...";0)elsematchSdl.get_window_from_ididwith|Ok_->id|Error_->loop(id+1)inloop0(* treatment of clicks *)(***********************)(* We just copy the Sdl event structure *)typebc_static={window_id:int;button_which:Tsdl.Sdl.uint32;(* the mouse instance id, or SDL_TOUCH_MOUSEID; see Remarks for details *)button_button:Tsdl.Sdl.uint8}typebc_dynamic={mutabletimestamp:int;(* Warning! not Tsdl.Sdl.uint32 *)mutablebutton_state:Tsdl.Sdl.button_state;mutablebutton_x:int;mutablebutton_y:int;}typebutton_click={mutablestatic:bc_static;dynamic:bc_dynamic}letempty_click()=letstatic={window_id=0;button_which=0l;button_button=0}inletdynamic={timestamp=0;button_state=Sdl.released;button_x=0;button_y=0}in{static;dynamic}letbutton_down_event=empty_click()letsingle_click_delay=300(* between button_down and button_up *)letdouble_click_delay=400(* between first button_up and second button_up *)letsingle_click=refNoneletdouble_click=refNonelettoo_fast=reffalseletcopy_from_eventevbc=letopenSdl.Eventinletstatic={window_id=getevmouse_button_window_id;button_which=getevmouse_button_which;button_button=getevmouse_button_button}inbc.static<-static;bc.dynamic.timestamp<-Int32.to_int(getevtimestamp);bc.dynamic.button_state<-getevmouse_button_state;bc.dynamic.button_x<-getevmouse_button_x;bc.dynamic.button_y<-getevmouse_button_y(* Should be called on every button_down. Remark: on my machine, any
SDL_FINGERDOWN is preceeded by SDL_MOUSEMOTION + SDL_MOUSEBUTTONDOWN (in this
order). *)letbutton_downev=flushE.mouse_button_down;flushE.finger_down;printddebug_event"Mouse button down...";copy_from_eventevbutton_down_event(* Should be called on every button_up. Remark: on my machine, any SDL_FINGERUP
is preceeded by SDL_MOUSEBUTTONUP *)letbutton_upev=flushE.mouse_button_up;flushE.finger_up;letb_up=empty_click()incopy_from_eventevb_up;ifb_up.static=button_down_event.staticthenlett=b_up.dynamic.timestampinlett0=button_down_event.dynamic.timestampintoo_fast:=t-t0<=2;if!too_fastthen(printddebug_event"Click was too fast. We disregard it";single_click:=None;double_click:=None)else(match!single_clickwith|None->ift-t0<single_click_delaythen(printddebug_event"Single click %d ms"(t-t0);single_click:=Somet;double_click:=None)else(printddebug_event"No click: too late";single_click:=None;double_click:=None)|Somest->ift-st<double_click_delaythen(printddebug_event"Double click";double_click:=Somet;single_click:=None)elseift-t0<single_click_delaythen(printddebug_event"Still Single click %d ms"(t-t0);single_click:=Somet;double_click:=None)else(printddebug_event"No click: too late";single_click:=None;double_click:=None))elseprintddebug_event"No click: not the same button"letwas_double_click()=!double_click<>Noneletwas_single_click()=(!single_click<>None)&&(!double_click=None)(* text input *)(* test if the shift mod (and only it) is pressed *)letshift_pressed()=letm=Sdl.get_mod_state()inm=Sdl.Kmod.lshift||m=Sdl.Kmod.rshift(* only ctrl *)letctrl_pressed()=letm=Sdl.get_mod_state()inm=Sdl.Kmod.ctrl||m=Sdl.Kmod.lctrl||m=Sdl.Kmod.rctrlletctrl_shift_pressed()=letm=Sdl.get_mod_state()inm=Sdl.Kmod.lctrllorSdl.Kmod.lshift||m=Sdl.Kmod.lctrllorSdl.Kmod.rshift||m=Sdl.Kmod.rctrllorSdl.Kmod.rshift||m=Sdl.Kmod.rctrllorSdl.Kmod.lshiftletmouse_left_button_pressed()=letm,_=Sdl.get_mouse_state()inInt32.logandmSdl.Button.lmask=Sdl.Button.lmask