123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426(** Windows. *)openMiscopenTsdltype_Events.ev+=|Close:(unit->bool)Events.ev(* Close request for window; callback should return [true] to prevent closing. *)(**/**)letkeyboard_state_emptyst=letmoduleA=Bigarray.Array1inletempty=reftrueinfori=0toA.dimst-1doletv=A.getstiinifv<>0then(Log.debug(funm->m"key %S is pressed"(Sdl.(get_key_name(get_key_from_scancodei))));empty:=false)done;!empty(**/**)(** A window is a widget containing one widget and interfacing
with SDL window to render and handle events.
Windows should not be created directly but through
{!App.create_window}, {!App.create_scrolled_window} and
{!App.popup_menu} so that events are propagated to them
*)classwindow?(class_="window")?name?props?(flags=Sdl.Window.(opengl))?(rflags=Sdl.Renderer.software)?resizable?x?y?w?htitle=letrflags=Tsdl.Sdl.Renderer.(rflags+targettexture)inletflags=matchresizablewith|None->flags|Sometrue->Sdl.Window.(flags+resizable)|Somefalse->Sdl.Window.(flags-resizable)inlet(autosize_w,autosize_h,w,h)=ifSdl.Window.(testflagsresizable)then(false,false,Option.value~default:1w,Option.value~default:1h)elseletautosize_w,w=matchwwithNone->(true,1)|Somew->(false,w)inletautosize_h,h=matchhwithNone->(true,1)|Someh->(false,h)in(autosize_w,autosize_h,w,h)inletwin=matchSdl.create_windowtitle?x?y~w~hflagswith|Error(`Msgmsg)->Misc.sdl_errormsg|Okwin->wininletrenderer=matchSdl.(create_renderer~flags:rflagswin)with|Error(`Msgmsg)->Misc.sdl_errormsg|Okr->rinlet()=matchSdl.set_render_draw_blend_moderendererSdl.Blend.mode_blendwith|Error(`Msgmsg)->Misc.sdl_errormsg|Okx->xinletwinid=Sdl.get_window_idwininletrender_mutex=Lwt_mutex.create()inletwith_renderer_lockf=Lwt_mutex.with_lockrender_mutex(fun()->frenderer)inobject(self)inheritBin.bin~class_?name?props()assuper(** The SDL window. *)methodwindow=win(** The SDL window id. *)methodwindow_id=winid(** Set window title. *)methodset_titlestr=Sdl.set_window_titlewinstr(**/**)method!top_window=Somewinvalmutablecursor=Sdl.get_cursor()method!cursor=matchcursorwith|None->Log.warn(funm->m"%s#cursor: None"self#me);None|Somec->Somecmethod!to_desktop_coords~x~y=let(wx,wy)=Sdl.get_window_positionwinin(wx+x,wy+y)(**/**)(** Returns SDL window position as [(x,y)]. *)methodposition=let(x,y)=Sdl.get_window_positionwininLog.debug(funm->m"%s#position = (%d,%d)"self#mexy);(x,y)(** Returns the SDL window size. *)methodsize=Sdl.get_window_sizewin(**/**)methodrenderer=renderervalmutableto_render=(Layer.Map.empty:G.tLayer.Map.t)(**/**)(** [w#resize ~w ~h] resize the window with width [w] and height [h]. *)methodresize~w~h=self#update_geometry~w~h()(**/**)methodprivateupdate_geometry=let(w0,h0)=self#sizeinfun?(w=w0)?(h=h0)()->letw=ifautosize_wthenself#min_widthelsewinleth=ifautosize_hthenself#min_heightelsehinif(w0<>w||h0<>h)thenSdl.set_window_sizeself#window~w~h;Log.debug(funm->m"%s#update_geometry => w=%d, h=%d"self#mewh);self#set_geometry{gwithw;h}valmutableon_close=(fun()->())(**/**)(** Set callback on closing. *)methodset_on_closef=on_close<-f(** Closes the window (destroying it). *)methodclose=(tryon_close()withe->Log.err(funm->m"%s#close: %s"self#me(Printexc.to_stringe)));self#destroy(**/**)method!get_focus=Log.debug(funm->m"%s#get_focus return Some(%b)"self#me(self#get_pProps.has_focus));(* release previous focus, but do not cal self#release_focus, since
this would update our has_focus props; start with child.
If focus is not released, then return None. *)matchmatchchildwith|None->true|Somew->w#release_focuswith|true->Some(self#get_pProps.has_focus)|false->Nonemethod!grab_focus?last()=Sdl.raise_windowself#window;(* In case the window did not get the focus, force it.
It seems to happen when a window already had focus before,
typically for dialog windows not destroyed but just hidden. *)matchSdl.get_keyboard_focus()with|SomewwhenSdl.get_window_idw=winid->true|_->matchSdl.set_window_input_focusself#windowwith|Error(`Msgmsg)->Log.err(funm->m"%s: %s"self#memsg);false|Ok()->true(*super#grab_focus ?last ()*)(* Give focus to the first focusable widget, using grab_focus on
child. *)method!child_focus_next(w:Widget.widget)=w#grab_focus()method!child_focus_prev(w:Widget.widget)=w#grab_focus~last:true()valmutablelast_key_event=Nonemethodon_window_event(e:Sdl.event)=letmust_render=matchSdl.Event.(window_event_enum(getewindow_event_id))with|`Close->(letkeep=self#trigger_eventClose()inLog.debug(funm->m"%s: `Close event => keep = %b"self#mekeep);matchkeepwith|true->true|false->self#close;false)|`Enter->(* when entering a window with a button pressed on another one and
not released, the `Enter event occurs only when releasing the button,
and mouse_state coords seem to refer to previous window;
we use global state and convert to our window coordinates *)let(_,(x,y))=Sdl.get_global_mouse_state()inlet(wx,wy)=Sdl.get_window_positionwininletpos=(x-wx,y-wy)inlet_=self#on_sdl_event_down~oldpos:None(Somepos)einfalse|`Exposed->true|`Focus_gained->let_=(* if a key is already pressed, set last_key_event to
`Keyboard_state to block key press events which occured
when another window had the focus. Indeed, since the
key may still be pressed, the window gaining the focus will receive
key press events, which is not what we want. So we will block
key press events when the keyboard_state is the same as when we
got the focus, except is there is no key pressed.*)letstate=Sdl.get_keyboard_state()inifnot(keyboard_state_emptystate)thenlast_key_event<-Some(`Keyboard_statestate);self#set_pProps.has_focustrue;matchself#set_has_focustruewith|true->true(* has_focus was set on a widget *)|false->(* no widget already has the focus previously, let's use
grab_focus to give to the first one *)super#grab_focus()inLog.debug(funm->m"%s: focus gained %a"self#meWidget.pp_widget_treeself#wtree);false|`Focus_lost->Log.debug(funm->m"%s: focus lost"self#me);self#set_pProps.has_focusfalse;let_=self#set_has_focusfalseinfalse|`Hidden|`Hit_test|`Leave->let_=self#on_sdl_event_down~oldpos:NoneNoneeinfalse|`Maximized|`Minimized->self#update_geometry();true|`Moved->false|`Resized->self#update_geometry();true|`Restored|`Shown->false|`Size_changed->self#update_geometry();true|`Take_focus|`Unknown_->falseinifmust_renderthen(self#need_render~layer:Layer.Baseg;)methodon_root_event~oldposposev=letev_type=Sdl.Event.(enum(getevtyp))in(*Log.warn (fun m -> m "%s#on_root_event last_key_event=%s"
self#me (match last_key_event with
| None -> "NONE"
| Some `Text_editing -> "`Text_editing"
| Some `Text_input -> "`Text_input"
| Some `Handled_key_press -> "`Handled_key_press"));*)matchev_type,last_key_eventwith|`Text_input,Some`Handled_key_press->(*Log.warn (fun m -> m "text_input event blocked");*)false|`Key_down,Some`Keyboard_statestwhenst=Sdl.get_keyboard_state()->Log.debug(funm->m"key press blocked according to keyboard state");false|`Key_down,Some`Text_editing->(*Log.warn (fun m -> m "key_down event blocked");*)false|_->lethandled=self#on_sdl_event_down~oldposposevin(* block next text input event if key_down was handled, unblock
when a key_down event was not handled *)matchev_typewith|`Key_down->let()=matchhandled,last_key_eventwith|false,Some_->last_key_event<-None;|false,None->()|true,_whenSdl.is_text_input_active()->last_key_event<-Some`Handled_key_press|true,Some_->last_key_event<-None|true,None->()inhandled|`Key_up->last_key_event<-None;handled|`Text_input->last_key_event<-Some`Text_input;handled|`Text_editingwhenSdl.Event.(getevtext_editing_length)=0->(* to pump spurious Text_editing events when window gets the focus *)handled|`Text_editing->last_key_event<-Some`Text_editing;handled|_->handledmethod!render_child~layerrend~offset~g_none~g_child=trysuper#render_child~layerrend~offset~g_none~g_childwithe->Log.err(funm->m"%s#render_child: %s %s"self#me(Printexc.to_stringe)(Printexc.get_backtrace()));methodrender_window?layers()=Log.debug(funm->m"%s#render"self#me);let>()=Sdl.set_render_targetrendererNoneintryletgeom=let(w,h)=self#sizeinletg={G.x=0;y=0;w;h}inmatchlayerswith|None->Log.debug(funm->m"%s#render_window reset-painting %a"self#meG.ppg);Render.fill_rectrendererNoneself#bg_color_now;self#render_child~layer:Layer.Baserenderer~offset:(0,0)~g_none:g~g_child:g;Someg|Somemap->Layer.Map.fold(funlayerg_to_renderacc->matchG.intergg_to_renderwith|None->None|Someg->iflayer=Layer.Basethen(Log.debug(funm->m"%s#render_window reset-painting %a"self#meG.ppg);Render.fill_rectrenderer(Someg)self#bg_color_now);self#render_child~layerrenderer~offset:(0,0)~g_none:g~g_child:g;matchaccwith|None->Someg|Someacc->Some(G.uniongacc))mapNonein(*let> () = Sdl.set_render_target renderer None in
let> () = Sdl.render_set_clip_rect renderer None in*)matchgeomwith|None->()|Somegeom->Log.debug(funm->m"%s render_copy and present %a"self#meG.ppgeom);ifnotself#sensitivethenself#render_insensitiverenderer~offset:(0,0)geom;Sdl.render_presentrendererwith|e->Log.err(funm->m"%s#render_window: %s"self#me(Printexc.to_stringe))methodrender~layerrenderer~offsetg=Log.debug(funm->m"%s#render should not be called"self#me)methodrender_if_needed=ifnot(Layer.Map.is_emptyto_render)then(letlayers=to_renderinto_render<-Layer.Map.empty;self#render_window~layers())method!destroy=(trysuper#destroywithe->Log.err(funm->m"%s#destroy: %s"self#me(Printexc.to_stringe)));Sdl.destroy_windowwinmethodadd_to_render~layerg=Log.debug(funm->m"%s#add_to_render ~layer:%a %a"self#meLayer.pplayerG.ppg);(matchlayerwith|Layer.Base->self#add_to_render~layer:Layer.Menug|Layer.Menu->());matchLayer.Map.find_optlayerto_renderwith|None->ifg.w=0||g.h=0then()elseto_render<-Layer.Map.addlayergto_render|Somegr->letres=G.unionggrinLog.debug(funm->m"%s#add_to_render ~layer:%a => %a"self#meLayer.pplayerG.ppres);to_render<-Layer.Map.addlayerresto_rendermethodneed_render~layergeom=matchself#need_renderinggeomwith|None->()|Somegeom->self#add_to_render~layergeom(* method child_need_render ~layer geom =
Log.debug (fun m -> m "%s#child_need_render ~layer:%a on %a"
self#me Layer.pp layer G.pp geom);
*)methodneed_resize=super#need_resize;self#update_geometry()(**/**)(** Show SDL window.*)methodshow=Sdl.show_windowwin(** Hide SDL window. *)methodhide=Sdl.hide_windowwin(** [w#move ~x ~y] moves SDL window to [(x,y)]. *)methodmove~x~y=Log.debug(funm->m"%s#move ~x:%d ~y:%d"self#mexy);Sdl.set_window_positionwin~x~yinitializerwith_renderer<-Somewith_renderer_lock;self#update_geometry();(* we must pump events and move again the window or else
something goes wrong with sdl and window manager, window
is not placed where asked. *)matchx,ywith|Somex,Somey->Tsdl.Sdl.pump_events();self#move~x~y|_->()end(**/**)letwindow?class_?name?props?flags?rflags?resizable?(show=true)?x?y?w?htitle=letw=newwindow?class_?name?props?flags?rflags?resizable?x?y?w?htitleinifnotshowthenw#hide;w