Driver.cs 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. using System;
  2. using System.Collections.Generic;
  3. using Unix.Terminal;
  4. namespace Terminal {
  5. public enum Color
  6. {
  7. Black,
  8. Blue,
  9. Green,
  10. Cyan,
  11. Red,
  12. Magenta,
  13. Brown,
  14. Gray,
  15. DarkGray,
  16. BrightBlue,
  17. BrightGreen,
  18. BrighCyan,
  19. BrightRed,
  20. BrightMagenta,
  21. BrightYellow,
  22. White
  23. }
  24. public struct Attribute {
  25. internal int value;
  26. public Attribute (int v)
  27. {
  28. value = v;
  29. }
  30. public static implicit operator int (Attribute c) => c.value;
  31. public static implicit operator Attribute (int v) => new Attribute (v);
  32. }
  33. public class ColorScheme {
  34. public Attribute Normal;
  35. public Attribute Focus;
  36. public Attribute HotNormal;
  37. public Attribute HotFocus;
  38. public Attribute Marked => HotNormal;
  39. public Attribute MarkedSelected => HotFocus;
  40. }
  41. public static class Colors {
  42. public static ColorScheme Base, Dialog, Menu, Error;
  43. }
  44. public abstract class ConsoleDriver {
  45. public abstract int Cols {get;}
  46. public abstract int Rows {get;}
  47. public abstract void Init ();
  48. public abstract void Move (int col, int row);
  49. public abstract void AddCh (int ch);
  50. public abstract void AddStr (string str);
  51. public abstract void PrepareToRun ();
  52. public abstract void Refresh ();
  53. public abstract void End ();
  54. public abstract void RedrawTop ();
  55. public abstract void SetAttribute (Attribute c);
  56. // Set Colors from limit sets of colors
  57. public abstract void SetColors (ConsoleColor foreground, ConsoleColor background);
  58. // Advanced uses - set colors to any pre-set pairs, you would need to init_color
  59. // that independently with the R, G, B values.
  60. public abstract void SetColors (short foreColorId, short backgroundColorId);
  61. public abstract void DrawFrame (Rect region, bool fill);
  62. Rect clip;
  63. public Rect Clip {
  64. get => clip;
  65. set => this.clip = value;
  66. }
  67. }
  68. public class CursesDriver : ConsoleDriver {
  69. public override int Cols => Curses.Cols;
  70. public override int Rows => Curses.Lines;
  71. // Current row, and current col, tracked by Move/AddCh only
  72. int ccol, crow;
  73. bool needMove;
  74. public override void Move (int col, int row)
  75. {
  76. ccol = col;
  77. crow = row;
  78. if (Clip.Contains (col, row)) {
  79. Curses.move (row, col);
  80. needMove = false;
  81. } else {
  82. Curses.move (Clip.Y, Clip.X);
  83. needMove = true;
  84. }
  85. }
  86. public override void AddCh (int ch)
  87. {
  88. if (Clip.Contains (ccol, crow)) {
  89. if (needMove) {
  90. Curses.move (crow, ccol);
  91. needMove = false;
  92. }
  93. Curses.addch (ch);
  94. } else
  95. needMove = true;
  96. ccol++;
  97. }
  98. public override void AddStr (string str)
  99. {
  100. // TODO; optimize this to determine if the str fits in the clip region, and if so, use Curses.addstr directly
  101. foreach (var c in str)
  102. AddCh ((int) c);
  103. }
  104. public override void Refresh() => Curses.refresh ();
  105. public override void End() => Curses.endwin ();
  106. public override void RedrawTop() => window.redrawwin ();
  107. public override void SetAttribute (Attribute c) => Curses.attrset (c.value);
  108. public Curses.Window window;
  109. static short last_color_pair = 16;
  110. static Attribute MakeColor (short f, short b)
  111. {
  112. Curses.InitColorPair (++last_color_pair, f, b);
  113. return new Attribute () { value = Curses.ColorPair (last_color_pair) };
  114. }
  115. int [,] colorPairs = new int [16, 16];
  116. public override void SetColors (ConsoleColor foreground, ConsoleColor background)
  117. {
  118. int f = (short) foreground;
  119. int b = (short)background;
  120. var v = colorPairs [f, b];
  121. if ((v & 0x10000) == 0) {
  122. b = b & 0x7;
  123. bool bold = (f & 0x8) != 0;
  124. f = f & 0x7;
  125. v = MakeColor ((short)f, (short)b) | (bold ? Curses.A_BOLD : 0);
  126. colorPairs [(int)foreground, (int)background] = v | 0x1000;
  127. }
  128. SetAttribute (v & 0xffff);
  129. }
  130. Dictionary<int, int> rawPairs = new Dictionary<int, int> ();
  131. public override void SetColors (short foreColorId, short backgroundColorId)
  132. {
  133. int key = (((ushort)foreColorId << 16)) | (ushort) backgroundColorId;
  134. if (!rawPairs.TryGetValue (key, out var v)) {
  135. v = MakeColor (foreColorId, backgroundColorId);
  136. rawPairs [key] = v;
  137. }
  138. SetAttribute (v);
  139. }
  140. public override void PrepareToRun()
  141. {
  142. Curses.timeout (-1);
  143. }
  144. public override void DrawFrame (Rect region, bool fill)
  145. {
  146. int width = region.Width;
  147. int height = region.Height;
  148. int b;
  149. Move (region.X, region.Y);
  150. AddCh (Curses.ACS_ULCORNER);
  151. for (b = 0; b < width - 2; b++)
  152. AddCh (Curses.ACS_HLINE);
  153. AddCh (Curses.ACS_URCORNER);
  154. for (b = 1; b < height - 1; b++) {
  155. Move (region.X, region.Y + b);
  156. AddCh (Curses.ACS_VLINE);
  157. if (fill) {
  158. for (int x = 1; x < width - 1; x++)
  159. AddCh (' ');
  160. } else
  161. Move (region.X + width - 1, region.Y + b);
  162. AddCh (Curses.ACS_VLINE);
  163. }
  164. Move (region.X, region.Y + height - 1);
  165. AddCh (Curses.ACS_LLCORNER);
  166. for (b = 0; b < width - 2; b++)
  167. AddCh (Curses.ACS_HLINE);
  168. AddCh (Curses.ACS_LRCORNER);
  169. }
  170. public override void Init()
  171. {
  172. if (window != null)
  173. return;
  174. try {
  175. window = Curses.initscr ();
  176. } catch (Exception e){
  177. Console.WriteLine ("Curses failed to initialize, the exception is: " + e);
  178. }
  179. Curses.raw ();
  180. Curses.noecho ();
  181. Curses.Window.Standard.keypad (true);
  182. Colors.Base = new ColorScheme ();
  183. Colors.Dialog = new ColorScheme ();
  184. Colors.Menu = new ColorScheme ();
  185. Colors.Error = new ColorScheme ();
  186. Clip = new Rect (0, 0, Cols, Rows);
  187. if (Curses.HasColors){
  188. Curses.StartColor ();
  189. Curses.UseDefaultColors ();
  190. Colors.Base.Normal = MakeColor (Curses.COLOR_WHITE, Curses.COLOR_BLUE);
  191. Colors.Base.Focus = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_CYAN);
  192. Colors.Base.HotNormal = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_BLUE);
  193. Colors.Base.HotFocus = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_CYAN);
  194. Colors.Menu.Normal = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_CYAN);
  195. Colors.Menu.Focus = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_CYAN);
  196. Colors.Menu.HotNormal = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_BLACK);
  197. Colors.Menu.HotFocus = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_BLACK);
  198. Colors.Dialog.Normal = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_WHITE);
  199. Colors.Dialog.Focus = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_CYAN);
  200. Colors.Dialog.HotNormal = MakeColor (Curses.COLOR_BLUE, Curses.COLOR_WHITE);
  201. Colors.Dialog.HotFocus = MakeColor (Curses.COLOR_BLUE, Curses.COLOR_CYAN);
  202. Colors.Error.Normal = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_RED);
  203. Colors.Error.Focus = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_WHITE);
  204. Colors.Error.HotNormal = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_RED);
  205. Colors.Error.HotFocus = Colors.Error.HotNormal;
  206. } else {
  207. Colors.Base.Normal = Curses.A_NORMAL;
  208. Colors.Base.Focus = Curses.A_REVERSE;
  209. Colors.Base.HotNormal = Curses.A_BOLD;
  210. Colors.Base.HotFocus = Curses.A_BOLD | Curses.A_REVERSE;
  211. Colors.Menu.Normal = Curses.A_REVERSE;
  212. Colors.Menu.Focus = Curses.A_NORMAL;
  213. Colors.Menu.HotNormal = Curses.A_BOLD;
  214. Colors.Menu.HotFocus = Curses.A_NORMAL;
  215. Colors.Dialog.Normal = Curses.A_REVERSE;
  216. Colors.Dialog.Focus = Curses.A_NORMAL;
  217. Colors.Dialog.HotNormal = Curses.A_BOLD;
  218. Colors.Dialog.HotFocus = Curses.A_NORMAL;
  219. Colors.Error.Normal = Curses.A_BOLD;
  220. Colors.Error.Focus = Curses.A_BOLD | Curses.A_REVERSE;
  221. Colors.Error.HotNormal = Curses.A_BOLD | Curses.A_REVERSE;
  222. Colors.Error.HotFocus = Curses.A_REVERSE;
  223. }
  224. }
  225. }
  226. }