Desktop.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. /*
  2. ConFrames - Gui Stuff for Console Windows
  3. Copyright (C) 2017-2018 Topten Software.
  4. ConFrames is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. ConFrames is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with ConFrames. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. using System;
  16. using System.Collections.Generic;
  17. using System.Linq;
  18. using System.Runtime.InteropServices;
  19. namespace ConFrames
  20. {
  21. public class Desktop
  22. {
  23. public Desktop(int width, int height)
  24. {
  25. // Save stdout
  26. _stdout = Interop.GetStdHandle(Interop.STD_OUTPUT_HANDLE);
  27. // Create buffer
  28. _buffer = Interop.CreateConsoleScreenBuffer(Interop.GENERIC_READWRITE, (uint)0, IntPtr.Zero, Interop.CONSOLE_TEXTMODE_BUFFER, IntPtr.Zero);
  29. // Default colors
  30. ActiveBorderBackgroundColor = ConsoleColor.Blue;
  31. ActiveBorderLineColor = ConsoleColor.White;
  32. InactiveBorderLineColor = ConsoleColor.Gray;
  33. InactiveBorderBackgroundColor = ConsoleColor.DarkBlue;
  34. // Default desktop size
  35. DesktopSize = new Size(width, height);
  36. DesktopColor = ConsoleColor.DarkBlue;
  37. }
  38. // Handle to the screen and stdout buffers
  39. IntPtr _stdout;
  40. IntPtr _buffer;
  41. // Desktop size
  42. Size _desktopSize;
  43. public Size DesktopSize
  44. {
  45. get { return _desktopSize; }
  46. set
  47. {
  48. try
  49. {
  50. // Try to set it
  51. Interop.SetBufferAndScreenSize(_buffer, (short)value.Width, (short)value.Height);
  52. // Save it
  53. _desktopSize = value;
  54. }
  55. catch
  56. {
  57. try
  58. {
  59. // try to set it back
  60. Interop.SetBufferAndScreenSize(_buffer, (short)_desktopSize.Width, (short)_desktopSize.Height);
  61. }
  62. catch { }
  63. throw;
  64. }
  65. }
  66. }
  67. // List of all windows
  68. List<Window> _windows = new List<Window>();
  69. // Register a new window
  70. internal void AddWindow(Window window)
  71. {
  72. if (_windows.IndexOf(window)<0)
  73. {
  74. _windows.Add(window);
  75. InvalidateDesktop();
  76. }
  77. }
  78. // Remove a window
  79. internal void RemoveWindow(Window window)
  80. {
  81. _windows.Remove(window);
  82. InvalidateDesktop();
  83. }
  84. // Colors
  85. public ConsoleColor ActiveBorderBackgroundColor
  86. {
  87. get;
  88. set;
  89. }
  90. public ConsoleColor ActiveBorderLineColor
  91. {
  92. get;
  93. set;
  94. }
  95. public ConsoleColor InactiveBorderBackgroundColor
  96. {
  97. get;
  98. set;
  99. }
  100. public ConsoleColor InactiveBorderLineColor
  101. {
  102. get;
  103. set;
  104. }
  105. // Color of area behind all windows
  106. ConsoleColor _desktopColor;
  107. public ConsoleColor DesktopColor
  108. {
  109. get
  110. {
  111. return _desktopColor;
  112. }
  113. set
  114. {
  115. _desktopColor = value;
  116. InvalidateDesktop();
  117. }
  118. }
  119. // Called just before repainting the screen
  120. protected virtual void OnWillUpdate()
  121. {
  122. }
  123. // Called just after repainting the screen
  124. protected virtual void OnDidUpdate()
  125. {
  126. }
  127. // Called just before waiting for input
  128. protected virtual void OnEnterProcessing()
  129. {
  130. }
  131. // Called just after waiting for input
  132. protected virtual void OnLeaveProcessing()
  133. {
  134. }
  135. // Preview received keys - return true if handled
  136. protected virtual bool OnPreviewKey(ConsoleKeyInfo key)
  137. {
  138. if (PreviewKey != null)
  139. return PreviewKey(key);
  140. return false;
  141. }
  142. // Handler
  143. public Func<ConsoleKeyInfo, bool> PreviewKey;
  144. // Get/Set the currently active window
  145. public Window ActiveWindow
  146. {
  147. get { return _windows.Count == 0 ? null : _windows[_windows.Count - 1]; }
  148. set
  149. {
  150. var oldActive = ActiveWindow;
  151. int pos = _windows.IndexOf(value);
  152. if (pos < _windows.Count-1)
  153. {
  154. _windows.RemoveAt(pos);
  155. _windows.Add(value);
  156. }
  157. if (oldActive!=ActiveWindow)
  158. {
  159. Invalidate(oldActive);
  160. Invalidate(ActiveWindow);
  161. }
  162. }
  163. }
  164. // Invalidate flags
  165. bool _needRedraw = false;
  166. bool _needClear = false;
  167. public void Invalidate(Window w)
  168. {
  169. _needRedraw = true;
  170. }
  171. public void InvalidateDesktop()
  172. {
  173. _needClear = true;
  174. _needRedraw = true;
  175. }
  176. // Update
  177. public void Update()
  178. {
  179. // Quit if we don't need a redraw
  180. if (_needRedraw)
  181. {
  182. // Notify
  183. OnWillUpdate();
  184. // Clear flag
  185. _needRedraw = false;
  186. // Do we need to clear?
  187. if (_needClear)
  188. {
  189. _needClear = false;
  190. // Get screens size
  191. Interop.CONSOLE_SCREEN_BUFFER_INFO info;
  192. Interop.GetConsoleScreenBufferInfo(_buffer, out info);
  193. // Create buffer
  194. CharInfo[] buf = new CharInfo[info.dwSize.X * info.dwSize.Y];
  195. // Clear buffer
  196. var defAttributes = (ushort)((ushort)0 | ((ushort)_desktopColor << 4));
  197. for (int i = 0; i < buf.Length; ++i)
  198. {
  199. buf[i].Attributes = defAttributes;
  200. buf[i].Char = (char)' ';
  201. }
  202. // Copy it
  203. var r = new Interop.SmallRect()
  204. {
  205. Top = (short)0,
  206. Left = (short)0,
  207. Right = (short)info.dwSize.X,
  208. Bottom = (short)info.dwSize.Y,
  209. };
  210. Interop.WriteConsoleOutput(_buffer,
  211. buf,
  212. new Interop.Coord() { X = (short)info.dwSize.X, Y = info.dwSize.Y },
  213. new Interop.Coord() { X = 0, Y = 0 },
  214. ref r);
  215. }
  216. // Draw all windows
  217. foreach (var w in _windows)
  218. {
  219. var buf = w.Draw();
  220. var r = new Interop.SmallRect()
  221. {
  222. Top = (short)w.FrameRectangle.Top,
  223. Left = (short)w.FrameRectangle.Left,
  224. Right = (short)w.FrameRectangle.Right,
  225. Bottom = (short)w.FrameRectangle.Bottom,
  226. };
  227. Interop.WriteConsoleOutput(_buffer,
  228. buf,
  229. new Interop.Coord() { X = (short)w.FrameRectangle.Width, Y = (short)w.FrameRectangle.Height },
  230. new Interop.Coord() { X = 0, Y = 0 },
  231. ref r);
  232. }
  233. // Finished
  234. OnDidUpdate();
  235. }
  236. // Reposition cursor according to how the active window wants it
  237. var active = ActiveWindow;
  238. if (active != null)
  239. {
  240. if (active.CursorPosition.X >= 0 && active.CursorPosition.Y >= 0 &&
  241. active.CursorPosition.X < active.FrameRectangle.Width - 2 &&
  242. active.CursorPosition.Y < active.FrameRectangle.Height - 2)
  243. {
  244. Interop.SetConsoleCursorPosition(_buffer, new Interop.Coord(
  245. (short)(active.FrameRectangle.Left + active.CursorPosition.X + 1),
  246. (short)(active.FrameRectangle.Top + active.CursorPosition.Y + 1)
  247. ));
  248. Interop.SetConsoleCursorVisible(_buffer, active.CursorVisible);
  249. }
  250. else
  251. {
  252. Interop.SetConsoleCursorVisible(_buffer, false);
  253. }
  254. }
  255. else
  256. {
  257. Interop.SetConsoleCursorVisible(_buffer, false);
  258. }
  259. }
  260. // Cancel from the process loop
  261. bool _continueProcessing = false;
  262. public void EndProcessing()
  263. {
  264. _continueProcessing = false;
  265. }
  266. // Process
  267. public void Process()
  268. {
  269. // Notfiy
  270. OnEnterProcessing();
  271. // Make active
  272. ViewMode = ViewMode.Desktop;
  273. // Process loop
  274. _continueProcessing = true;
  275. while (_continueProcessing)
  276. {
  277. // Do any update
  278. Update();
  279. // Bring console to front
  280. BringToFront();
  281. // Read the next key
  282. var key = Console.ReadKey(true);
  283. // Switch windows?
  284. if (key.Key == ConsoleKey.Tab)
  285. {
  286. if (key.Modifiers == ConsoleModifiers.Control)
  287. {
  288. if (_windows.Any())
  289. {
  290. var top = _windows[_windows.Count - 1];
  291. _windows.RemoveAt(_windows.Count-1);
  292. _windows.Insert(0, top);
  293. _needRedraw = true;
  294. }
  295. continue;
  296. }
  297. if (key.Modifiers == (ConsoleModifiers.Control | ConsoleModifiers.Shift))
  298. {
  299. if (_windows.Any())
  300. {
  301. var top = _windows[0];
  302. _windows.RemoveAt(0);
  303. _windows.Add(top);
  304. _needRedraw = true;
  305. }
  306. continue;
  307. }
  308. }
  309. // Toggle to stdout?
  310. if (key.Key == ConsoleKey.F4 && key.Modifiers == 0)
  311. {
  312. ViewMode = ViewMode.StdOut;
  313. Console.ReadKey(true);
  314. ViewMode = ViewMode.Desktop;
  315. continue;
  316. }
  317. // Preview key event
  318. if (OnPreviewKey(key))
  319. continue;
  320. // Send key to the active window
  321. var aw = ActiveWindow;
  322. if (aw!= null)
  323. {
  324. aw.OnKey(key);
  325. }
  326. }
  327. // Notify
  328. OnLeaveProcessing();
  329. // Finished
  330. return;
  331. }
  332. // Bring the console window to foreground
  333. IntPtr _oldForegroundWindow;
  334. public void BringToFront()
  335. {
  336. _oldForegroundWindow = Interop.GetActiveWindow();
  337. Interop.SetForegroundWindow(Interop.GetConsoleWindow());
  338. }
  339. // Restore the old active foreground window
  340. public void RestoreForegroundWindow()
  341. {
  342. if (_oldForegroundWindow==IntPtr.Zero)
  343. {
  344. _oldForegroundWindow = System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle;
  345. }
  346. if (_oldForegroundWindow !=IntPtr.Zero)
  347. {
  348. Interop.SetForegroundWindow(_oldForegroundWindow);
  349. _oldForegroundWindow = IntPtr.Zero;
  350. }
  351. }
  352. // Set the view mode (desktop or stdout)
  353. ViewMode _viewMode = ViewMode.StdOut;
  354. public ViewMode ViewMode
  355. {
  356. get
  357. {
  358. return _viewMode;
  359. }
  360. set
  361. {
  362. if (_viewMode !=value)
  363. {
  364. _viewMode = value;
  365. if (_viewMode==ViewMode.Desktop)
  366. {
  367. Interop.SetConsoleActiveScreenBuffer(_buffer);
  368. }
  369. else
  370. {
  371. Interop.SetConsoleActiveScreenBuffer(_stdout);
  372. }
  373. }
  374. }
  375. }
  376. }
  377. }