Driver.cs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. using System;
  2. using System.Collections.Generic;
  3. using Unix.Terminal;
  4. namespace Terminal {
  5. public struct Color {
  6. internal int value;
  7. public Color (int v)
  8. {
  9. value = v;
  10. }
  11. public static implicit operator int (Color c) => c.value;
  12. public static implicit operator Color (int v) => new Color (v);
  13. }
  14. public class ColorScheme {
  15. public Color Normal;
  16. public Color Focus;
  17. public Color HotNormal;
  18. public Color HotFocus;
  19. public Color Marked => HotNormal;
  20. public Color MarkedSelected => HotFocus;
  21. }
  22. public static class Colors {
  23. public static ColorScheme Base, Dialog, Menu, Error;
  24. }
  25. public abstract class ConsoleDriver {
  26. public abstract int Cols {get;}
  27. public abstract int Rows {get;}
  28. public abstract void Init ();
  29. public abstract void Move (int col, int row);
  30. public abstract void AddCh (int ch);
  31. public abstract void AddStr (string str);
  32. public abstract void PrepareToRun ();
  33. public abstract void Refresh ();
  34. public abstract void End ();
  35. public abstract void RedrawTop ();
  36. public abstract void SetColor (Color c);
  37. public abstract void DrawFrame (Rect region, bool fill);
  38. Rect clip;
  39. public Rect Clip {
  40. get => clip;
  41. set => this.clip = value;
  42. }
  43. }
  44. public class CursesDriver : ConsoleDriver {
  45. public override int Cols => Curses.Cols;
  46. public override int Rows => Curses.Lines;
  47. // Current row, and current col, tracked by Move/AddCh only
  48. int ccol, crow;
  49. bool needMove;
  50. public override void Move (int col, int row)
  51. {
  52. ccol = col;
  53. crow = row;
  54. if (Clip.Contains (col, row)) {
  55. Curses.move (row, col);
  56. needMove = false;
  57. } else {
  58. Curses.move (Clip.Y, Clip.X);
  59. needMove = true;
  60. }
  61. }
  62. public override void AddCh (int ch)
  63. {
  64. if (Clip.Contains (ccol, crow)) {
  65. if (needMove) {
  66. Curses.move (crow, ccol);
  67. needMove = false;
  68. }
  69. Curses.addch (ch);
  70. } else
  71. needMove = true;
  72. ccol++;
  73. }
  74. public override void AddStr (string str)
  75. {
  76. // TODO; optimize this to determine if the str fits in the clip region, and if so, use Curses.addstr directly
  77. foreach (var c in str)
  78. AddCh ((int) c);
  79. }
  80. public override void Refresh() => Curses.refresh ();
  81. public override void End() => Curses.endwin ();
  82. public override void RedrawTop() => window.redrawwin ();
  83. public override void SetColor (Color c) => Curses.attrset (c.value);
  84. public Curses.Window window;
  85. static short last_color_pair;
  86. static Color MakeColor (short f, short b)
  87. {
  88. Curses.InitColorPair (++last_color_pair, f, b);
  89. return new Color () { value = Curses.ColorPair (last_color_pair) };
  90. }
  91. public override void PrepareToRun()
  92. {
  93. Curses.timeout (-1);
  94. }
  95. public override void DrawFrame (Rect region, bool fill)
  96. {
  97. int width = region.Width;
  98. int height = region.Height;
  99. int b;
  100. Move (region.X, region.Y);
  101. AddCh (Curses.ACS_ULCORNER);
  102. for (b = 0; b < width - 2; b++)
  103. AddCh (Curses.ACS_HLINE);
  104. AddCh (Curses.ACS_URCORNER);
  105. for (b = 1; b < height - 1; b++) {
  106. Move (region.X, region.Y + b);
  107. AddCh (Curses.ACS_VLINE);
  108. if (fill) {
  109. for (int x = 1; x < width - 1; x++)
  110. AddCh (' ');
  111. } else
  112. Move (region.X + width - 1, region.Y + b);
  113. AddCh (Curses.ACS_VLINE);
  114. }
  115. Move (region.X, region.Y + height - 1);
  116. AddCh (Curses.ACS_LLCORNER);
  117. for (b = 0; b < width - 2; b++)
  118. AddCh (Curses.ACS_HLINE);
  119. AddCh (Curses.ACS_LRCORNER);
  120. }
  121. public override void Init()
  122. {
  123. if (window != null)
  124. return;
  125. try {
  126. window = Curses.initscr ();
  127. } catch (Exception e){
  128. Console.WriteLine ("Curses failed to initialize, the exception is: " + e);
  129. }
  130. Curses.raw ();
  131. Curses.noecho ();
  132. Curses.Window.Standard.keypad (true);
  133. Colors.Base = new ColorScheme ();
  134. Colors.Dialog = new ColorScheme ();
  135. Colors.Menu = new ColorScheme ();
  136. Colors.Error = new ColorScheme ();
  137. Clip = new Rect (0, 0, Cols, Rows);
  138. if (Curses.HasColors){
  139. Curses.StartColor ();
  140. Curses.UseDefaultColors ();
  141. Colors.Base.Normal = MakeColor (Curses.COLOR_WHITE, Curses.COLOR_BLUE);
  142. Colors.Base.Focus = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_CYAN);
  143. Colors.Base.HotNormal = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_BLUE);
  144. Colors.Base.HotFocus = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_CYAN);
  145. Colors.Menu.Normal = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_CYAN);
  146. Colors.Menu.Focus = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_CYAN);
  147. Colors.Menu.HotNormal = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_BLACK);
  148. Colors.Menu.HotFocus = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_BLACK);
  149. Colors.Dialog.Normal = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_WHITE);
  150. Colors.Dialog.Focus = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_CYAN);
  151. Colors.Dialog.HotNormal = MakeColor (Curses.COLOR_BLUE, Curses.COLOR_WHITE);
  152. Colors.Dialog.HotFocus = MakeColor (Curses.COLOR_BLUE, Curses.COLOR_CYAN);
  153. Colors.Error.Normal = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_RED);
  154. Colors.Error.Focus = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_WHITE);
  155. Colors.Error.HotNormal = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_RED);
  156. Colors.Error.HotFocus = Colors.Error.HotNormal;
  157. } else {
  158. Colors.Base.Normal = Curses.A_NORMAL;
  159. Colors.Base.Focus = Curses.A_REVERSE;
  160. Colors.Base.HotNormal = Curses.A_BOLD;
  161. Colors.Base.HotFocus = Curses.A_BOLD | Curses.A_REVERSE;
  162. Colors.Menu.Normal = Curses.A_REVERSE;
  163. Colors.Menu.Focus = Curses.A_NORMAL;
  164. Colors.Menu.HotNormal = Curses.A_BOLD;
  165. Colors.Menu.HotFocus = Curses.A_NORMAL;
  166. Colors.Dialog.Normal = Curses.A_REVERSE;
  167. Colors.Dialog.Focus = Curses.A_NORMAL;
  168. Colors.Dialog.HotNormal = Curses.A_BOLD;
  169. Colors.Dialog.HotFocus = Curses.A_NORMAL;
  170. Colors.Error.Normal = Curses.A_BOLD;
  171. Colors.Error.Focus = Curses.A_BOLD | Curses.A_REVERSE;
  172. Colors.Error.HotNormal = Curses.A_BOLD | Curses.A_REVERSE;
  173. Colors.Error.HotFocus = Curses.A_REVERSE;
  174. }
  175. }
  176. }
  177. }