KeyboardImpl.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. #nullable enable
  2. using System.Diagnostics;
  3. namespace Terminal.Gui.App;
  4. /// <summary>
  5. /// INTERNAL: Implements <see cref="IKeyboard"/> to manage keyboard input and key bindings at the Application level.
  6. /// <para>
  7. /// This implementation decouples keyboard handling state from the static <see cref="Application"/> class,
  8. /// enabling parallelizable unit tests and better testability.
  9. /// </para>
  10. /// <para>
  11. /// See <see cref="IKeyboard"/> for usage details.
  12. /// </para>
  13. /// </summary>
  14. internal class KeyboardImpl : IKeyboard
  15. {
  16. private Key _quitKey = Key.Esc; // Resources/config.json overrides
  17. private Key _arrangeKey = Key.F5.WithCtrl; // Resources/config.json overrides
  18. private Key _nextTabGroupKey = Key.F6; // Resources/config.json overrides
  19. private Key _nextTabKey = Key.Tab; // Resources/config.json overrides
  20. private Key _prevTabGroupKey = Key.F6.WithShift; // Resources/config.json overrides
  21. private Key _prevTabKey = Key.Tab.WithShift; // Resources/config.json overrides
  22. /// <summary>
  23. /// Commands for Application.
  24. /// </summary>
  25. private readonly Dictionary<Command, View.CommandImplementation> _commandImplementations = new ();
  26. /// <inheritdoc/>
  27. public IApplication? Application { get; set; }
  28. /// <inheritdoc/>
  29. public KeyBindings KeyBindings { get; internal set; } = new (null);
  30. /// <inheritdoc/>
  31. public Key QuitKey
  32. {
  33. get => _quitKey;
  34. set
  35. {
  36. KeyBindings.Replace (_quitKey, value);
  37. _quitKey = value;
  38. }
  39. }
  40. /// <inheritdoc/>
  41. public Key ArrangeKey
  42. {
  43. get => _arrangeKey;
  44. set
  45. {
  46. KeyBindings.Replace (_arrangeKey, value);
  47. _arrangeKey = value;
  48. }
  49. }
  50. /// <inheritdoc/>
  51. public Key NextTabGroupKey
  52. {
  53. get => _nextTabGroupKey;
  54. set
  55. {
  56. KeyBindings.Replace (_nextTabGroupKey, value);
  57. _nextTabGroupKey = value;
  58. }
  59. }
  60. /// <inheritdoc/>
  61. public Key NextTabKey
  62. {
  63. get => _nextTabKey;
  64. set
  65. {
  66. KeyBindings.Replace (_nextTabKey, value);
  67. _nextTabKey = value;
  68. }
  69. }
  70. /// <inheritdoc/>
  71. public Key PrevTabGroupKey
  72. {
  73. get => _prevTabGroupKey;
  74. set
  75. {
  76. KeyBindings.Replace (_prevTabGroupKey, value);
  77. _prevTabGroupKey = value;
  78. }
  79. }
  80. /// <inheritdoc/>
  81. public Key PrevTabKey
  82. {
  83. get => _prevTabKey;
  84. set
  85. {
  86. KeyBindings.Replace (_prevTabKey, value);
  87. _prevTabKey = value;
  88. }
  89. }
  90. /// <inheritdoc/>
  91. public event EventHandler<Key>? KeyDown;
  92. /// <inheritdoc/>
  93. public event EventHandler<Key>? KeyUp;
  94. /// <summary>
  95. /// Initializes keyboard bindings.
  96. /// </summary>
  97. public KeyboardImpl ()
  98. {
  99. AddKeyBindings ();
  100. }
  101. /// <inheritdoc/>
  102. public bool RaiseKeyDownEvent (Key key)
  103. {
  104. //ebug.Assert (App.Application.MainThreadId == Thread.CurrentThread.ManagedThreadId);
  105. //Logging.Debug ($"{key}");
  106. // TODO: Add a way to ignore certain keys, esp for debugging.
  107. //#if DEBUG
  108. // if (key == Key.Empty.WithAlt || key == Key.Empty.WithCtrl)
  109. // {
  110. // Logging.Debug ($"Ignoring {key}");
  111. // return false;
  112. // }
  113. //#endif
  114. // TODO: This should match standard event patterns
  115. KeyDown?.Invoke (null, key);
  116. if (key.Handled)
  117. {
  118. return true;
  119. }
  120. if (Application?.Popover?.DispatchKeyDown (key) is true)
  121. {
  122. return true;
  123. }
  124. if (Application?.Top is null)
  125. {
  126. if (Application?.TopLevels is { })
  127. {
  128. foreach (Toplevel topLevel in Application.TopLevels.ToList ())
  129. {
  130. if (topLevel.NewKeyDownEvent (key))
  131. {
  132. return true;
  133. }
  134. if (topLevel.Modal)
  135. {
  136. break;
  137. }
  138. }
  139. }
  140. }
  141. else
  142. {
  143. if (Application.Top.NewKeyDownEvent (key))
  144. {
  145. return true;
  146. }
  147. }
  148. bool? commandHandled = InvokeCommandsBoundToKey (key);
  149. if(commandHandled is true)
  150. {
  151. return true;
  152. }
  153. return false;
  154. }
  155. /// <inheritdoc/>
  156. public bool RaiseKeyUpEvent (Key key)
  157. {
  158. if (Application?.Initialized != true)
  159. {
  160. return true;
  161. }
  162. KeyUp?.Invoke (null, key);
  163. if (key.Handled)
  164. {
  165. return true;
  166. }
  167. // TODO: Add Popover support
  168. if (Application?.TopLevels is { })
  169. {
  170. foreach (Toplevel topLevel in Application.TopLevels.ToList ())
  171. {
  172. if (topLevel.NewKeyUpEvent (key))
  173. {
  174. return true;
  175. }
  176. if (topLevel.Modal)
  177. {
  178. break;
  179. }
  180. }
  181. }
  182. return false;
  183. }
  184. /// <inheritdoc/>
  185. public bool? InvokeCommandsBoundToKey (Key key)
  186. {
  187. bool? handled = null;
  188. // Invoke any Application-scoped KeyBindings.
  189. // The first view that handles the key will stop the loop.
  190. // foreach (KeyValuePair<Key, KeyBinding> binding in KeyBindings.GetBindings (key))
  191. if (KeyBindings.TryGet (key, out KeyBinding binding))
  192. {
  193. if (binding.Target is { })
  194. {
  195. if (!binding.Target.Enabled)
  196. {
  197. return null;
  198. }
  199. handled = binding.Target?.InvokeCommands (binding.Commands, binding);
  200. }
  201. else
  202. {
  203. bool? toReturn = null;
  204. foreach (Command command in binding.Commands)
  205. {
  206. toReturn = InvokeCommand (command, key, binding);
  207. }
  208. handled = toReturn ?? true;
  209. }
  210. }
  211. return handled;
  212. }
  213. /// <inheritdoc/>
  214. public bool? InvokeCommand (Command command, Key key, KeyBinding binding)
  215. {
  216. if (!_commandImplementations.ContainsKey (command))
  217. {
  218. throw new NotSupportedException (
  219. @$"A KeyBinding was set up for the command {command} ({key}) but that command is not supported by Application."
  220. );
  221. }
  222. if (_commandImplementations.TryGetValue (command, out View.CommandImplementation? implementation))
  223. {
  224. CommandContext<KeyBinding> context = new (command, null, binding); // Create the context here
  225. return implementation (context);
  226. }
  227. return null;
  228. }
  229. /// <summary>
  230. /// <para>
  231. /// Sets the function that will be invoked for a <see cref="Command"/>.
  232. /// </para>
  233. /// <para>
  234. /// If AddCommand has already been called for <paramref name="command"/> <paramref name="f"/> will
  235. /// replace the old one.
  236. /// </para>
  237. /// </summary>
  238. /// <remarks>
  239. /// <para>
  240. /// This version of AddCommand is for commands that do not require a <see cref="ICommandContext"/>.
  241. /// </para>
  242. /// </remarks>
  243. /// <param name="command">The command.</param>
  244. /// <param name="f">The function.</param>
  245. private void AddCommand (Command command, Func<bool?> f) { _commandImplementations [command] = ctx => f (); }
  246. internal void AddKeyBindings ()
  247. {
  248. _commandImplementations.Clear ();
  249. // Things Application knows how to do
  250. AddCommand (
  251. Command.Quit,
  252. () =>
  253. {
  254. Application?.RequestStop ();
  255. return true;
  256. }
  257. );
  258. AddCommand (
  259. Command.Suspend,
  260. () =>
  261. {
  262. Application?.Driver?.Suspend ();
  263. return true;
  264. }
  265. );
  266. AddCommand (
  267. Command.NextTabStop,
  268. () => Application?.Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop));
  269. AddCommand (
  270. Command.PreviousTabStop,
  271. () => Application?.Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop));
  272. AddCommand (
  273. Command.NextTabGroup,
  274. () => Application?.Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup));
  275. AddCommand (
  276. Command.PreviousTabGroup,
  277. () => Application?.Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabGroup));
  278. AddCommand (
  279. Command.Refresh,
  280. () =>
  281. {
  282. Application?.LayoutAndDraw (true);
  283. return true;
  284. }
  285. );
  286. AddCommand (
  287. Command.Arrange,
  288. () =>
  289. {
  290. View? viewToArrange = Application?.Navigation?.GetFocused ();
  291. // Go up the superview hierarchy and find the first that is not ViewArrangement.Fixed
  292. while (viewToArrange is { SuperView: { }, Arrangement: ViewArrangement.Fixed })
  293. {
  294. viewToArrange = viewToArrange.SuperView;
  295. }
  296. if (viewToArrange is { })
  297. {
  298. return viewToArrange.Border?.EnterArrangeMode (ViewArrangement.Fixed);
  299. }
  300. return false;
  301. });
  302. //SetKeysToHardCodedDefaults ();
  303. // Need to clear after setting the above to ensure actually clear
  304. // because set_QuitKey etc.. may call Add
  305. KeyBindings.Clear ();
  306. KeyBindings.Add (QuitKey, Command.Quit);
  307. KeyBindings.Add (NextTabKey, Command.NextTabStop);
  308. KeyBindings.Add (PrevTabKey, Command.PreviousTabStop);
  309. KeyBindings.Add (NextTabGroupKey, Command.NextTabGroup);
  310. KeyBindings.Add (PrevTabGroupKey, Command.PreviousTabGroup);
  311. KeyBindings.Add (ArrangeKey, Command.Arrange);
  312. KeyBindings.Add (Key.CursorRight, Command.NextTabStop);
  313. KeyBindings.Add (Key.CursorDown, Command.NextTabStop);
  314. KeyBindings.Add (Key.CursorLeft, Command.PreviousTabStop);
  315. KeyBindings.Add (Key.CursorUp, Command.PreviousTabStop);
  316. // TODO: Refresh Key should be configurable
  317. KeyBindings.Add (Key.F5, Command.Refresh);
  318. // TODO: Suspend Key should be configurable
  319. if (Environment.OSVersion.Platform == PlatformID.Unix)
  320. {
  321. KeyBindings.Add (Key.Z.WithCtrl, Command.Suspend);
  322. }
  323. }
  324. }