Application.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. //
  2. //
  3. // Pending:
  4. // - Check for NeedDisplay on the hierarchy and repaint
  5. // - Layout support
  6. //
  7. // Optimziations
  8. // - Add rendering limitation to the exposed area
  9. using System;
  10. using System.Collections.Generic;
  11. namespace Terminal {
  12. public class Responder {
  13. public virtual bool CanFocus => true;
  14. public bool HasFocus { get; internal set; }
  15. // Key handling
  16. public virtual void KeyDown (Event.Key kb) {}
  17. // Mouse events
  18. public virtual void MouseEvent (Event.Mouse me) {}
  19. }
  20. public class View : Responder {
  21. View container = null;
  22. View focused = null;
  23. public static ConsoleDriver Driver = Application.Driver;
  24. public static IList<View> empty = new List<View>(0).AsReadOnly ();
  25. List<View> subviews;
  26. public IList<View> Subviews => subviews == null ? empty : subviews.AsReadOnly ();
  27. internal bool NeedDisplay { get; private set; } = true;
  28. // The frame for the object
  29. Rect frame;
  30. // The offset of the first child view inside the view
  31. Point offset;
  32. // The frame for this view
  33. public Rect Frame {
  34. get => frame;
  35. set {
  36. frame = value;
  37. SetNeedsDisplay ();
  38. }
  39. }
  40. public View (Rect frame)
  41. {
  42. this.Frame = frame;
  43. }
  44. public void SetNeedsDisplay ()
  45. {
  46. NeedDisplay = true;
  47. }
  48. public void AddSubview (View view)
  49. {
  50. if (view == null)
  51. return;
  52. if (subviews == null)
  53. subviews = new List<View> ();
  54. subviews.Add (view);
  55. }
  56. public void GetRealRowCol (int col, int row, out int rcol, out int rrow)
  57. {
  58. // Computes the real row, col relative to the screen.
  59. rrow = row;
  60. rcol = col;
  61. var ccontainer = container;
  62. while (ccontainer != null){
  63. rrow += container.frame.Y;
  64. rcol += container.frame.X;
  65. ccontainer = ccontainer.container;
  66. }
  67. // The following ensures that the cursor is always in the screen boundaries.
  68. rrow = Math.Max (0, Math.Min (rrow, Driver.Rows-1));
  69. rcol = Math.Max (0, Math.Min (rcol, Driver.Cols-1));
  70. }
  71. public void Move (int col, int row)
  72. {
  73. GetRealRowCol (col, row, out var rcol, out var rrow);
  74. Driver.Move (rcol, rrow);
  75. }
  76. public virtual void PositionCursor ()
  77. {
  78. if (focused != null)
  79. focused.PositionCursor ();
  80. else
  81. Move (frame.X, frame.Y);
  82. }
  83. public void AddCh (int col, int row, int ch)
  84. {
  85. if (row < 0 || col < 0)
  86. return;
  87. if (row > frame.Height-1 || col > frame.Width-1)
  88. return;
  89. Move (col, row);
  90. Driver.AddCh (ch);
  91. }
  92. public virtual void Redraw ()
  93. {
  94. var clipRect = new Rect (offset, frame.Size);
  95. foreach (var view in subviews){
  96. if (view.NeedDisplay){
  97. if (view.Frame.IntersectsWith (clipRect)){
  98. view.Redraw ();
  99. }
  100. view.NeedDisplay = false;
  101. }
  102. }
  103. NeedDisplay = false;
  104. }
  105. public void SetFocus (View view)
  106. {
  107. if (view == null)
  108. return;
  109. if (!view.CanFocus)
  110. return;
  111. if (focused == view)
  112. return;
  113. if (focused != null)
  114. focused.HasFocus = false;
  115. focused = view;
  116. view.HasFocus = true;
  117. if (view != null)
  118. view.EnsureFocus ();
  119. focused.PositionCursor ();
  120. }
  121. public void EnsureFocus ()
  122. {
  123. if (focused == null)
  124. FocusFirst ();
  125. }
  126. public void FocusFirst ()
  127. {
  128. foreach (var view in subviews){
  129. if (view.CanFocus){
  130. SetFocus (view);
  131. return;
  132. }
  133. }
  134. }
  135. public void FocusLast ()
  136. {
  137. for (int i = subviews.Count; i > 0; ){
  138. i--;
  139. View v = subviews [i];
  140. if (v.CanFocus){
  141. SetFocus (v);
  142. return;
  143. }
  144. }
  145. }
  146. public bool FocusPrev ()
  147. {
  148. if (focused == null){
  149. FocusLast ();
  150. return true;
  151. }
  152. int focused_idx = -1;
  153. for (int i = subviews.Count; i > 0; ){
  154. i--;
  155. View w = subviews [i];
  156. if (w.HasFocus){
  157. if (w.FocusPrev ())
  158. return true;
  159. focused_idx = i;
  160. continue;
  161. }
  162. if (w.CanFocus && focused_idx != -1){
  163. focused.HasFocus = false;
  164. if (w.CanFocus)
  165. w.FocusLast ();
  166. SetFocus (w);
  167. return true;
  168. }
  169. }
  170. if (focused != null){
  171. focused.HasFocus = false;
  172. focused = null;
  173. }
  174. return false;
  175. }
  176. public bool FocusNext ()
  177. {
  178. if (focused == null){
  179. FocusFirst ();
  180. return focused != null;
  181. }
  182. int n = subviews.Count;
  183. int focused_idx = -1;
  184. for (int i = 0; i < n; i++){
  185. View w = subviews [i];
  186. if (w.HasFocus){
  187. if (w.FocusNext ())
  188. return true;
  189. focused_idx = i;
  190. continue;
  191. }
  192. if (w.CanFocus && focused_idx != -1){
  193. focused.HasFocus = false;
  194. if (w != null && w.CanFocus)
  195. w.FocusFirst ();
  196. SetFocus (w);
  197. return true;
  198. }
  199. }
  200. if (focused != null){
  201. focused.HasFocus = false;
  202. focused = null;
  203. }
  204. return false;
  205. }
  206. public virtual void LayoutSubviews ()
  207. {
  208. }
  209. }
  210. public class Toplevel : View {
  211. public Toplevel (Rect frame) : base (frame)
  212. {
  213. }
  214. public static Toplevel Create ()
  215. {
  216. return new Window (new Rect (0, 0, Driver.Cols, Driver.Rows));
  217. }
  218. }
  219. public class Window : Toplevel {
  220. View contentView;
  221. string title;
  222. public string Title {
  223. get => title;
  224. set {
  225. title = value;
  226. SetNeedsDisplay ();
  227. }
  228. }
  229. public Window (Rect frame, string title = null) : base (frame)
  230. {
  231. frame.Inflate (-1, -1);
  232. contentView = new View (frame);
  233. AddSubview (contentView);
  234. }
  235. public override void Redraw ()
  236. {
  237. base.Redraw ();
  238. }
  239. }
  240. public class Application {
  241. public static ConsoleDriver Driver = new CursesDriver ();
  242. public static Toplevel Top { get; private set; }
  243. public static Mono.Terminal.MainLoop MainLoop { get; private set; }
  244. static Stack<View> toplevels = new Stack<View> ();
  245. static Responder focus;
  246. public static void MakeFirstResponder (Responder newResponder)
  247. {
  248. if (newResponder == null)
  249. throw new ArgumentNullException ();
  250. throw new NotImplementedException ();
  251. }
  252. public static void Init ()
  253. {
  254. if (Top != null)
  255. return;
  256. MainLoop = new Mono.Terminal.MainLoop ();
  257. Top = Toplevel.Create ();
  258. focus = Top;
  259. MainLoop.AddWatch (0, Mono.Terminal.MainLoop.Condition.PollIn, x => {
  260. //ProcessChar ();
  261. return true;
  262. });
  263. }
  264. public class RunState : IDisposable {
  265. internal RunState (View view)
  266. {
  267. View = view;
  268. }
  269. internal View View;
  270. public void Dispose ()
  271. {
  272. Dispose (true);
  273. GC.SuppressFinalize(this);
  274. }
  275. public virtual void Dispose (bool disposing)
  276. {
  277. if (View != null){
  278. Application.End (View);
  279. View = null;
  280. }
  281. }
  282. }
  283. public void Run ()
  284. {
  285. Run (Top);
  286. }
  287. static public RunState Begin (View view)
  288. {
  289. if (view == null)
  290. throw new ArgumentNullException ("view");
  291. var rs = new RunState (view);
  292. Init ();
  293. Driver.PrepareToRun ();
  294. toplevels.Push (view);
  295. view.LayoutSubviews ();
  296. view.FocusFirst ();
  297. Redraw (view);
  298. view.PositionCursor ();
  299. Driver.Refresh ();
  300. return rs;
  301. }
  302. static public void End (RunState rs)
  303. {
  304. if (rs == null)
  305. throw new ArgumentNullException (nameof (rs));
  306. rs.Dispose ();
  307. }
  308. static void Shutdown ()
  309. {
  310. Driver.End ();
  311. }
  312. static void Redraw (View view)
  313. {
  314. view.Redraw ();
  315. Driver.Refresh ();
  316. }
  317. static void Refresh (View view)
  318. {
  319. view.Redraw ();
  320. Driver.Refresh ();
  321. }
  322. public static void Refresh ()
  323. {
  324. Driver.RedrawTop ();
  325. View last = null;
  326. foreach (var v in toplevels){
  327. v.Redraw ();
  328. last = v;
  329. }
  330. if (last != null)
  331. last.PositionCursor ();
  332. Driver.Refresh ();
  333. }
  334. internal static void End (View view)
  335. {
  336. if (toplevels.Peek () != view)
  337. throw new ArgumentException ("The view that you end with must be balanced");
  338. toplevels.Pop ();
  339. if (toplevels.Count == 0)
  340. Shutdown ();
  341. else
  342. Refresh ();
  343. }
  344. public void Run (View view)
  345. {
  346. }
  347. }
  348. }