123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735(** One-line text-editor widget *)(* bug bizarre: après execution de l'exemple 12 (et lorsqu'on joue un peu avec),
emacs freeze complètement (obligé de tuer) dès qu'on sélectionne une
région *)(* cf:
https://wiki.libsdl.org/Tutorials/TextInput#CandidateList
*)openTsdlopenB_utilsmoduleUtf8=B_utf8moduleTheme=B_thememoduleVar=B_varmoduleTrigger=B_triggermoduleDraw=B_drawmoduleLabel=B_labelmoduleMouse=B_mousetypeselection=|Empty|Startofint(* ne sert pas à grand chose *)|Activeof(int*int);;(* n1 should be <= n2 *)typefilter=string->booltypet={keys:(stringlist)Var.t;(* = each string is just a letter (cf utf8 encoding problems...) *)cursor:(Draw.textureoption)Var.t;(* make this a global variable? *)cursor_font:(Label.font)Var.t;(* make this a Theme variable? *)cursor_pos:intVar.t;cursor_char:string;render:(Draw.textureoption)Var.t;offset:intVar.t;(* = this is the x-offset of the section to be rendered on
screen, with respect to the whole text if it was rendered on
a full line *)font:(Label.font)Var.t;size:int;(* font size *)active:boolVar.t;room_x:intVar.t;(* physical x *)(* TODO this is a hack to access room geometry (we need this to treat click events). do better? *)selection:selectionVar.t;max_size:int;(* max number of letters *)prompt:string;(* text to display when there is no user input *)filter:filter;(* which letters are accepted *)}lettriggers=Sdl.Event.[text_editing;text_input;key_down;key_up]letno_filter_=trueletuint_filters=List.mems["0";"1";"2";"3";"4";"5";"6";"7";"8";"9"]letand_filterf1f2=functions->(f1s)&&(f2s)letdefault_font=Label.File!Theme.text_fontletcreate?(max_size=2048)?(prompt="Enter text")?(size=Theme.text_font_size)?(filter=no_filter)?(font=default_font)text=Draw.ttf_init();letkeys=Utf8.splittextin{keys=Var.createkeys;cursor=Var.createNone;cursor_font=Var.create(Label.FileTheme.fa_font);cursor_pos=Var.create0;cursor_char=Theme.fa_symbol"tint";render=Var.createNone;offset=Var.create0;font=Var.createfont;size;active=Var.createfalse;room_x=Var.create0;selection=Var.createEmpty;max_size;prompt;filter;}letunloadti=beginmatchVar.getti.renderwith|None->()|Sometex->beginVar.setti.renderNone;Draw.forget_texturetexendend;matchVar.getti.cursorwith|None->()|Sometex->beginVar.setti.cursorNone;Draw.forget_texturetexend(* TODO *)letfree=unload(* TODO free font ? *)letresize_sizeti=unloadtilettextti=String.concat""(Var.getti.keys)letis_activeti=Var.getti.activeletclearti=lettexo=Var.getti.renderinVar.setti.renderNone;do_optiontexoDraw.forget_textureletstopti=printddebug_event"Stopping text input";ifSdl.is_text_input_active()thenSdl.stop_text_input();clearti;Var.setti.activefalse(* Because there is a length test, it should be placed ad the end of all
modifications of ti *)letsettikeys=ifkeys<>Var.getti.keysthenbeginletkeys=ifList.lengthkeys>ti.max_sizethen(printddebug_memory"Warning: text_input was truncated because it should not exceed \
%u symbols"ti.max_size;stopti;lethead,_=split_listkeysti.max_sizeinhead)elsekeysinVar.setti.keyskeys;Var.setti.cursor_pos(min(Var.getti.cursor_pos)(List.lengthkeys));cleartiendletkill_selectionti=matchVar.getti.selectionwith|Active(n1,n2)->lethead1,tail1=split_list(Var.getti.keys)n1inlet_,tail2=split_listtail1(n2-n1)inVar.setti.cursor_posn1;Var.setti.selectionEmpty;setti(List.flatten[head1;tail2]);|_->()(* better to inline ? *)letunselectti=printddebug_board"Removing selection";Var.setti.selectionEmptyletselect_allti=printddebug_board"Select all text";letl=List.length(Var.getti.keys)inVar.setti.selection(Active(0,l));clearti(* Insert a list of letters *)letinsert_listtilist=kill_selectionti;letx=Var.getti.cursor_posinlethead,tail=split_list(Var.getti.keys)xinVar.setti.cursor_pos(x+(List.lengthlist));setti(List.flatten[head;list;tail])(* Insert a letter *)letinserttis=insert_listti[s](* Insert a whole string *)letinsert_texttitext=letlist=Utf8.splittextininsert_listtilistletseps=[" ";";";".";",";"/";":";"\\n";"\\t";"\\j";"?";"!"](* Find a word containg the cursor position *)letfind_wordti=letn=Var.getti.cursor_posinletdaeh,tail=split_list_rev(Var.getti.keys)ninletrecfind_sep~complementlistpos=matchlistwith|[]->pos|key::rest->if(notcomplement&&List.memkeyseps)||(complement&¬(List.memkeyseps))thenposelsefind_sep~complementrest(pos+1)iniftail=[]then(printddebug_board"No word found: we are at the end";Empty)elseletcursor_key=List.hdtailinletcomplement=List.memcursor_keysepsin(* if 'complement' is true, then we are on a separator, so we must
find a "word of separators" *)letleft=find_sep~complementdaeh0inletright=find_sep~complementtail0inprintddebug_board"Word found (%d,%d)"leftright;Active(n-left,n+right)letselect_wordti=letsel=find_wordtiinVar.setti.selectionsel;cleartiletctrl_pressed()=letm=Sdl.get_mod_state()inm=Sdl.Kmod.ctrl||m=Sdl.Kmod.lctrl||m=Sdl.Kmod.rctrlletstart_selectionti=letn=Var.getti.cursor_posinprintddebug_board"Starting text selection at %d"n;Var.setti.selection(Startn)(* Start selection on pressing SHIFT *)letshift_check_selti=ifTrigger.shift_pressed()then(ifVar.getti.selection=Emptythenstart_selectionti)elseunselecttiletbackspaceti=ifVar.getti.selection<>Emptythenkill_selectiontielseletx=Var.getti.cursor_posinifx>0thenlethead,tail=split_list(Var.getti.keys)(x-1)inlettail'=matchtailwith|[]->printddebug_error"This should not happen in backspace";[]|_::rest->restinVar.setti.cursor_pos(x-1);setti(List.flatten[head;tail'])(* Move cursor to the left *)letleftti=shift_check_selti;letx=Var.getti.cursor_posinclearti;Var.setti.cursor_pos(max0(x-1))(* Move cursor to the right *)letrightti=shift_check_selti;letx=Var.getti.cursor_posinclearti;Var.setti.cursor_pos(min(List.length(Var.getti.keys))(x+1))(* Move to beginning of line *)lethometi=shift_check_selti;clearti;Var.setti.cursor_pos0(* Move to end of line *)letlastti=shift_check_selti;clearti;Var.setti.cursor_pos(List.length(Var.getti.keys))(*** input ***)letactivateti=printddebug_event"Activating text_input";ifSdl.is_text_input_active()thenbeginprintd(debug_error+debug_board+debug_event+debug_user)"You cannot have several Text_input active at the same time."(* The bad scenario is the following: you activate ti1, but ti2 was still
active, and is soon disabled, leading to Sdl.stop_text_input. This
means that no text_input event will be sent to ti1... *)end;Sdl.start_text_input();Var.setti.activetrue;clearti(* Validate selection from starting point to current cursor_pos *)letmake_selectionti=matchVar.getti.selectionwith|Empty->()|Startn0->letn=Var.getti.cursor_posinifn<>n0then(printddebug_board"Make selection [%d,%d]"n0n;Var.setti.selection(Active(minn0n,maxn0n)))else(Var.setti.selectionEmpty)|Active_->Var.setti.selectionEmpty(*** clipboard ***)(* Retrieve the string corresponding to the selection *)letselection_textti=matchVar.getti.selectionwith|Active(n1,n2)->let_,tail=split_list(Var.getti.keys)n1inlethead,_=split_listtail(n2-n1)inString.concat""head|_->""(* Copy to clipboard *)letcopyti=lettext=selection_texttiiniftext<>""thenbeginprintddebug_memory"Copy to clipboard: [%s]"text;go(Sdl.set_clipboard_texttext)end(* Copy and kill *)letkillti=copyti;kill_selectionti(* Paste from clipboard *)letpasteti=ifSdl.has_clipboard_text()thenlettext=go(Sdl.get_clipboard_text())ininsert_texttitext(* keyboard events *)(* Treat the text events *)(* DOC: *)(* SDL_Scancode values are used to represent the physical location of a keyboard
key on the keyboard. *)(* SDL_Keycode values are mapped to the current layout of the keyboard and
correlate to an SDL_Scancode *)letreceive_keytiev=ifis_activetithenlet(* in principe, if not active, text-input events are already disabled, but
one could still receive keyboard input events. This is why we have to
double check here *)openSdl.EventinmatchTrigger.event_kindevwith|`Text_input->(* a letter is recognized *)lets=getevtext_input_textinifti.filterstheninserttis|`Text_editing->print_endline"Text composing mode"(* TODO:
Update the composition text.
Update the cursor position.
Update the selection length (if any). *)|`Key_down->(matchgetevkeyboard_keycodewith|cwhenc=Sdl.K.backspace->backspaceti|cwhenc=Sdl.K.left->leftti|cwhenc=Sdl.K.right->rightti|cwhenc=Sdl.K.up->hometi|cwhenc=Sdl.K.home->hometi|cwhenc=Sdl.K.down->lastti|cwhenc=Sdl.K.kend->lastti|cwhenc=Sdl.K.return->stopti|cwhenc=Sdl.K.a&&ctrl_pressed()->select_allti|cwhenc=Sdl.K.c&&ctrl_pressed()->copyti(* : desactivate this for debugging the emacs problem *)|cwhenc=Sdl.K.x&&ctrl_pressed()->killti|cwhenc=Sdl.K.v&&ctrl_pressed()->pasteti|c->(printddebug_event"==> Key down event discarded.";printddebug_event"Key=[%s], mod=%u, Keycode:%u"(Sdl.get_key_namec)(Sdl.get_mod_state())c))|`Key_up->(matchgetevkeyboard_keycodewith|cwhenc=Sdl.K.lshift->make_selectionti|cwhenc=Sdl.K.rshift->make_selectionti|c->(printddebug_event"==> Key up event discarded.";printddebug_event"Key=[%s], mod=%u, Keycode:%u"(Sdl.get_key_namec)(Sdl.get_mod_state())c))|_->printddebug_warning"Warning: Event should not happen here"(************* display ***********)(* In the "display" function, the input text is drawn on the "surf" surface.
Then surf is copied onto a larger surface, "box", to accommodate for
underline. Then, box is clipped to its visible part (due to scrolling if the
text exceed the size of the widget) into the "visible" surface. The visible
surface gives the final widget texture. Nothing can be drawn outside the
"visible" surface. [box], and [visible] have same height. The cursor is a
separate texture (it's difficult to pre-blend everything due to SDL current
limitations on blend modes. Maybe soon we will have
https://wiki.libsdl.org/SDL_ComposeCustomBlendMode). *)(*
^
| bottom_margin (to center
<---------left_margin------------> v text vertically)
(put in the blit geom) |---visible---------------------------|
--------------box--|-------------------------------------|-----
| -----------|-----surf------------------------- | |
| | | | |
|<----->|BLA BLA |<------>|
|cursor ---------------------------------------------- cursor |
|width/2 _______ <--- underline ^ width |
| ^ <--- cursor | bottom_margin |
| v |
-------------------|-------------------------------------|-----
The "cursor_xpos" is computed wrt the origin of the surface "surf"
*)letmemo=reftrue(* use more memory (memoization) for speeding up display *)letdefault_size=(128,32)letleft_margin=2(* in logical pixels *)letbottom_margin=5(* used for underline *)(* TODO replace bottom_margin by cursor height (to compute) *)(* let cursor_width = 10
* let cursor_height = 9;; *)(* let cursor_thickness = 2;; *)letfontti=Label.get_font_varti.font(Theme.scale_intti.size)(* we cannot use Sdl.color type here if we want to memoize, since colors are
typically recreated by Sdl.Color.create... *)letrender_keyfontkeycolor=letcolor=Draw.create_colorcolorinletsurf=Draw.ttf_renderfontkeycolorinincrDraw.ttf_surfaces_in_memory;go(Sdl.set_surface_blend_modesurfSdl.Blend.mode_none);(* If we use blend, the semitransparent pixels will acquire the color of the
surface on which we blit (usually black, even if its alpha=0), which is not
good because that's not the final blitting. *)go(Sdl.set_surface_rlesurftrue);(* "If RLE is enabled, color key and alpha blending blits are much faster, but
the surface must be locked before directly accessing the pixels." (SDL
doc) *)surf(* memoize. Warning: do NOT free the resulting surfaces !! *)(* NOTE: it seems that this memoing does not really improve speed, but at least
it does not degrade speed... *)(* Warning: the arguments should not be mutable, otherwise memo is likely to
fail (equality problem). For instance, do not use Sdl.Color type instead of
(r,g,b,a) *)letrender_key,render_key_cleanup=if!memothenletf,table=memo3render_keyinletcleanup()=printddebug_graphics"Cleaning up %u SDL_TTF surfaces..."(Hashtbl.lengthtable);Hashtbl.iter(fun_surf->Draw.free_surfacesurf;decrDraw.ttf_surfaces_in_memory)table;Hashtbl.cleartableinf,cleanupelserender_key,fun()->()let()=Draw.at_cleanuprender_key_cleanup(** Return size of rendered text. It seems that Sdl.TTF.size_utf8 does not
always give the exact same result as size of blended-rendered
surface. Warning: thus, should use this only on single letters ! *)(* from http://www.libsdl.org/projects/SDL_ttf/docs/SDL_ttf_frame.html : *)(* Kerning is the process of spacing adjacent characters apart depending on the
actual two adjacent characters. This allows some characters to be closer to
each other than others. When kerning is not used, such as when using the
glyph metrics advance value, the characters will be spaced out at a constant
size that accomodates all pairs of adjacent characters. This would be the
maximum space between characters needed. There's currently no method to
retrieve the kerning for a pair of characters from SDL_ttf, However correct
kerning will be applied when a string of text is rendered instead of
individual glyphs. *)lettext_dimsfonttext=iftext=""then(printddebug_warning"[text_dims] called on empty string";0,0)(* OK ? or use 1,1 ?? *)elseletw,h=(* if !memo *)(* (\* if !memo, this is (maybe ?) faster to get surface_size than calling *)(* TTF.size_utf8. BUT this will save another surface (with color 0,0,0,0) i *)(* the table... *\) *)(* then let surf = render_key font text (0,0,0,0) in *)(* (\* : no need to free in case of memo *\) *)(* Sdl.get_surface_size surf *)(* else *)Label.physical_size_textfonttextinprintddebug_graphics"Size of '%s' = (%d,%d)."textwh;w,h(* we use all-purpose memo to memoize the kerning values. One could do
something more optimized, of course. *)lettext_dims=memo2text_dimsletbmax(x,y)(xx,yy)=imaxxxx,imaxyyy(* Initial size of the widget *)(* not scaled, in order to conform to all widgets size functions *)letsizeti=letw,h=matchVar.getti.renderwith|Sometex->Draw.tex_sizetex|None->bmax(text_dims(fontti)(ti.prompt))(text_dims(fontti)(String.concat""(Var.getti.keys)))inletw,h=Draw.unscale_size(w,h)in(w+2*left_margin(* this should probably be left_margin + cursor_width/2 *),h+2*bottom_margin)(* The bottom margin is also added at the top, in order to keep the text
vertically centered. *)lettext_widthfonts=letw,_=text_dimsfontsinw(* Return the cursor position with respect to the total text surface *)(* warning: this is already scaled... (physical pixels) *)letcursor_xpos?nti=letn=matchnwith|None->Var.getti.cursor_pos|Somen->ninlethead,_=split_list(Var.getti.keys)ninList.fold_left(funskey->s+text_width(fontti)key)0head(* Return cursor_pos corresponding to the x position *)letx_to_cursor_oldtix0=letroom_x=Var.getti.room_xinletx0=x0-room_x-(Theme.scale_int(left_margin-2))+(Var.getti.offset)in(* TODO optimize !!! *)letl=List.length(Var.getti.keys)inletrecloopn=ifn>=lthenlelseletcx=cursor_xpos~ntiinifcx>=x0thennelseloop(n+1)(* TODO: test if n is larger than max size ? *)inloop0(* Return cursor_pos corresponding to the x position *)letx_to_cursortix0=letroom_x=Var.getti.room_xinletchar_offset=ti.size/3in(* TODO, this should be roughly the half size of a char *)letx0=x0-room_x-(Theme.scale_int(left_margin+char_offset))+(Var.getti.offset)inletreclooplistcxn=matchlistwith|[]->n|key::rest->ifcx>=x0thennelseletadvance,_=text_dims(fontti)keyinlooprest(cx+advance)(n+1)inloop(Var.getti.keys)00(* Recall that none of the functions that are called by threads should call
video functions directly. *)(* Treat the click event to position the cursor, once the widget is active *)letclick_cursortiev=printddebug_event"Click cursor";letx0u,_=Mouse.pointer_posevinletx0=Theme.scale_intx0uin(* on pourrait éviter de faire unscale-scale *)Var.setti.cursor_pos(x_to_cursortix0);clearti(* This should be called on mouse_button_down *)letbutton_downtiev=ifis_activetithen(click_cursortiev;start_selectionti)(* This should be called on mouse_button_up *)letclicktiev=ifis_activetithenbeginifTrigger.was_double_click()thenselect_wordtielsebeginifTrigger.has_full_clickevthenclick_cursortiev;make_selectiontiendendelseifTrigger.has_full_clickevthenactivatetilettabtiev=ifSdl.Event.(getevkeyboard_keycode)=Sdl.K.tabthenbeginifnot(is_activeti)thenactivateti;select_alltiend(* We ask for redraw when mouse moves when button is pressed *)letmouse_selecttiev=printddebug_event"Mouse selection";click_cursortiev;clearti(* Render letter by letter so that x position is precise *)letdraw_keys?fgfontkeys=letcolor=ifkeys=[]thenDraw.(transpfaint_color)(* inutile ? *)elsedefaultfg(10,11,12,255)inprintddebug_graphics"Renders keys";letrecloopkeyssurfswh=matchkeyswith|[]->surfs,w,h|key::rest->letsurf=render_keyfontkeycolorinletdw,h=Sdl.get_surface_sizesurfinlooprest((surf,w)::surfs)(w+dw)hinletkeys=ifkeys=[]then[" "]elsekeysinletsurfs,tw,h=loopkeys[]00inletsurf,_=List.hdsurfsinprintddebug_graphics"Create total surface";lettotal_surf=Draw.create_surface~like:surftwhinprintddebug_graphics"Blit the letters on the surface";letrecdraw_loop=function|[]->()|(surf,w)::rest->letdst_rect=Sdl.Rect.create~x:w~y:0~w:0~h:0ingo(Sdl.blit_surface~src:surfNone~dst:total_surf(Somedst_rect));(* no free in case of memo: *)ifnot!memothenDraw.free_surfacesurf;draw_looprestindraw_loopsurfs;total_surf(* REMARK: instead of blitting surfaces, one could also use textures and SDL
RenderTarget ? *)letdisplaycanvaslayertig=(* TODO mettre un lock global ? *)letcursor=matchVar.getti.cursorwith|Somes->s|None->letcsize=imax3(2*(Theme.scale_intti.size)/3)inletcfont=Label.get_font_varti.cursor_fontcsizeinlets=draw_keyscfont[ti.cursor_char]~fg:Draw.(opaquecursor_color)in(* TODO use render_key, it should be faster *)lettex=Draw.create_texture_from_surfacecanvas.Draw.renderersinVar.setti.cursor(Sometex);Draw.free_surfaces;texinletcw,_=Draw.tex_sizecursorinlettex=matchVar.getti.renderwith|Somet->t|None->letstart_time=if!debugthenUnix.gettimeofday()else0.in(* =for debug only *)letkeys=Var.getti.keysinletfg=ifkeys<>[]thenDraw.(opaque!text_color)else(* if is_active ti then Draw.(opaque pale_grey) else *)Draw.(opaquefaint_color)inletkeys=ifkeys=[]&¬(is_activeti)then[ti.prompt]elsekeysinletsurf=draw_keys(fontti)keys~fgin(* TODO: draw only the relevent text, not everything. *)lettw,th=Sdl.get_surface_sizesurfin(* we need to make a slightly larger surface in order to have room for
underline and cursor *)letbox=Draw.create_surface~like:surf(tw+cw+cw/2)(th+Theme.scale_intbottom_margin)ingo(Sdl.set_surface_blend_modeboxSdl.Blend.mode_none);(* draw text on the larger surface, at (0,0) (upper-left corner)
preserving transparency information *)letrect=Draw.rect_translate(Sdl.get_clip_rectsurf)(cw/2,0)ingo(Sdl.set_surface_blend_modesurfSdl.Blend.mode_none);go(Sdl.blit_surface~src:surfNone~dst:box(Somerect));(* draw selection background: this will erase the corresponding text... *)(matchVar.getti.selectionwith|Active(n1,n2)->letx1=cursor_xpos~n:n1tiinletx2=cursor_xpos~n:n2tiinletsel_rect=Sdl.Rect.create~x:x1~y:0~w:(x2-x1)~h:thinletsel_rect_cw=Draw.rect_translatesel_rect(cw/2,0)inDraw.fill_rectbox(Somesel_rect_cw)Draw.(opaquesel_bg_color);(* now we reblit the text on the selection rectangle, this time with
blending *)letsel=draw_keys(fontti)keys~fg:Draw.(opaquesel_fg_color)in(* TODO: draw only the relevent text, not everything. *)go(Sdl.set_surface_blend_modeselSdl.Blend.mode_blend);go(Sdl.blit_surface~src:sel(Somesel_rect)~dst:box(Somesel_rect_cw))|Startn1->letx1=cursor_xpos~n:n1tiinletn2=Var.getti.cursor_posinletx2=cursor_xpos~n:n2tiinletsel_rect=Sdl.Rect.create~x:(minx1x2)~y:0~w:(abs(x2-x1))~h:thinletsel_rect_cw=Draw.rect_translatesel_rect(cw/2,0)in(* TODO regrouper avec ci-dessus ? *)Draw.fill_rectbox(Somesel_rect_cw)Draw.(opaquegrey);(* now we blend the text on the selection rectangle *)go(Sdl.set_surface_blend_modesurfSdl.Blend.mode_blend);go(Sdl.blit_surface~src:surf(Somesel_rect)~dst:box(Somesel_rect_cw))|_->());Draw.free_surfacesurf;ifVar.getti.activethenbegin(* draw underline *)letthick=Theme.scale_int1inlethline=Sdl.Rect.create~x:(cw/2)~y:(th(*+ bmargin - thick*))~w:tw~h:thickin(* Sdl.fill_rect : If the color value contains an alpha
component then the destination is simply filled with that
alpha information, no blending takes place. *)Draw.fill_rectbox(Somehline)Draw.(transpgrey);(* move the offset to have the cursor in the visible area *)letcx=cursor_xpostiinletoffset=Var.getti.offsetinletoffset=ifcx<=offset+cwthenmax0(cx-cw)elseifcx-offset>=g.Draw.w-cw-cw/2thenmintw(cx-g.Draw.w+cw+cw/2)elseoffsetinVar.setti.offsetoffsetend;(* we extract the visible part and save it as a texture, with all
transparency info (no blending) *)(* note: if we don't clip to the visible part, it is easy to reach the max
allowed texure width = 4096 *)letbw,bh=Sdl.get_surface_sizeboxinletoffset=Var.getti.offsetinletrect_b=Sdl.Rect.create~x:offset~y:0~w:(ming.Draw.w(bw-offset))~h:bhinletvisible=Draw.create_surface~like:box~color:Draw.none(Sdl.Rect.wrect_b)bhin(* this surface (converted to texture) will be *blended* on the canvas *)go(Sdl.blit_surface~src:box(Somerect_b)~dst:visibleNone);lettex=Draw.create_texture_from_surfacecanvas.Draw.renderervisibleinDraw.free_surfacebox;Draw.free_surfacevisible;Var.setti.render(Sometex);printddebug_graphics"Time for creating texture = %f s"(Unix.gettimeofday()-.start_time);texin(* finally we copy onto the canvas *)letopenDrawinletarea=geom_to_rectginSdl.set_text_input_rect(Somearea);(* TODO: this should be moved before
start_text_input *)Var.setti.room_xg.x;lettext_blit=copy_tex_to_layer~overlay:(Draw.Xoffset0)~voffset:g.voffsetcanvaslayertexarea(g.x+(Theme.scale_intleft_margin))(g.y+(Theme.scale_intbottom_margin))in(* we could instead have used a box surface of larger size, including margins,
and use tex_to_layer instead of copy_tex_to_layer *)ifis_activetithen(* (re...)compute cursor position *)(* The cursor is an additional blit. We don't pre-blend the two textures
(text+cursor) into a single blit, because the SDL current blend modes
don't allow this...
http://www.adriancourreges.com/blog/2017/05/09/beware-of-transparent-pixels/
*)let_,bh=tex_sizetexinletvoff=Theme.scale_int4inletcursor_g={gwithx=g.x+Theme.scale_intleft_margin+cursor_xposti-Var.getti.offset;y=g.y+bh-voff}in[text_blit;tex_to_layercanvaslayercursorcursor_g]else[text_blit]