Driver.cs 7.5 KB

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