123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209(*
* lTerm_ui.ml
* -----------
* Copyright : (c) 2011, Jeremie Dimino <jeremie@dimino.org>
* Licence : BSD3
*
* This file is a part of Lambda-Term.
*)openLTerm_geomletreturn,(>>=)=Lwt.return,Lwt.(>>=)(* +-----------------------------------------------------------------+
| The UI type |
+-----------------------------------------------------------------+ *)(* State of an UI. *)typestate=|Init(* The UI has not yet been drawn. *)|Loop(* The UI is running. *)|Stop(* The UI has been stopped. *)typet={term:LTerm.t;(* The terminal used for the UI. *)draw:t->LTerm_draw.matrix->unit;(* The draw function. *)mode:LTerm.mode;(* The previous mode of the terminal. *)mutablestate:state;(* State of the UI. *)restore_state:bool;(* Whether to restore the state of the terminal when quiting. *)mutablesize:LTerm_geom.size;(* The current size of the UI. *)mutablematrix_a:LTerm_draw.matrix;mutablematrix_b:LTerm_draw.matrix;(* The two matrices used for the rendering. *)mutablecursor_visible:bool;(* The cursor visible state. *)mutablecursor_position:LTerm_geom.coord;(* The cursor position. *)mutabledraw_queued:bool;(* Is a draw operation queued ? *)mutabledrawer:unitLwt.t;(* The thread drawing the terminal. *)mutabledrawing:bool;(* Are we drawing ? *)draw_error_push:exnoption->unit;draw_error_stream:exnLwt_stream.t;(* Stream used to send drawing error to [loop]. *)}letcheckui=ifui.state=Stopthenfailwith"The has been quited"(* +-----------------------------------------------------------------+
| Creation/quiting |
+-----------------------------------------------------------------+ *)letcreateterm?(save_state=true)draw=LTerm.enter_raw_modeterm>>=funmode->(ifsave_statethenLTerm.save_statetermelsereturn())>>=fun()->letstream,push=Lwt_stream.create()inreturn{term=term;draw=draw;mode=mode;state=Init;restore_state=save_state;size=LTerm.sizeterm;matrix_a=[||];matrix_b=[||];cursor_visible=false;cursor_position={row=0;col=0};draw_queued=false;drawer=return();drawing=false;draw_error_push=push;draw_error_stream=stream;}letquitui=checkui;ui.state<-Stop;ui.drawer>>=fun()->LTerm.leave_raw_modeui.termui.mode>>=fun()->ifui.restore_statethenLTerm.show_cursorui.term>>=fun()->LTerm.load_stateui.termelsereturn()(* +-----------------------------------------------------------------+
| Drawing |
+-----------------------------------------------------------------+ *)letimmediate_drawui=fun()->Lwt.catch(fun()->(* Wait a bit in order not to redraw too often. *)Lwt.pause()>>=fun()->ui.draw_queued<-false;ifui.state=Stopthenreturn()elsebegin(* Allocate the first matrix if needed. *)ifui.matrix_a=[||]thenui.matrix_a<-LTerm_draw.make_matrixui.size;(* Draw the screen *)ui.drawing<-true;(tryui.drawuiui.matrix_awithexn->ui.drawing<-false;raiseexn);ui.drawing<-false;(* Rendering. *)LTerm.hide_cursorui.term>>=fun()->LTerm.render_updateui.termui.matrix_bui.matrix_a>>=fun()->beginifui.cursor_visiblethenLTerm.gotoui.termui.cursor_position>>=fun()->LTerm.show_cursorui.termelsereturn()end>>=fun()->LTerm.flushui.term>>=fun()->(* Swap the two matrices. *)leta=ui.matrix_aandb=ui.matrix_binui.matrix_a<-b;ui.matrix_b<-a;return()end)(funexn->ui.draw_error_push(Someexn);return())letdrawui=checkui;ui.state<-Loop;(* If a draw operation is already queued, do nothing. *)ifnotui.draw_queuedthen(* Wait for draw operation to finish before starting new one *)ui.drawer<-ui.drawer>>=immediate_drawui(* +-----------------------------------------------------------------+
| Accessors |
+-----------------------------------------------------------------+ *)letsizeui=checkui;ui.sizeletcursor_visibleui=checkui;ui.cursor_visibleletset_cursor_visibleuistate=checkui;ifstate<>ui.cursor_visiblethenbeginui.cursor_visible<-state;ifui.state=Loop&¬ui.drawingthendrawuiendletcursor_positionui=checkui;ui.cursor_positionletset_cursor_positionuicoord=checkui;ifcoord<>ui.cursor_positionthenbeginui.cursor_position<-coord;ifui.state=Loop&¬ui.drawingthendrawuiend(* +-----------------------------------------------------------------+
| Loop |
+-----------------------------------------------------------------+ *)letwaitui=checkui;ifui.state=Initthendrawui;Lwt.pick[LTerm.read_eventui.term;Lwt_stream.nextui.draw_error_stream>>=Lwt.fail]>>=funev->matchevwith|LTerm_event.Resizesize->ui.size<-size;(* New size, discard current matrices. *)ui.matrix_a<-[||];ui.matrix_b<-[||];drawui;returnev|_->returnev