2
0

ApplicationPopover.cs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. #nullable enable
  2. using System.Diagnostics;
  3. namespace Terminal.Gui.App;
  4. /// <summary>
  5. /// Helper class for support of <see cref="IPopover"/> views for <see cref="Application"/>. Held by
  6. /// <see cref="Application.Popover"/>
  7. /// </summary>
  8. public sealed class ApplicationPopover : IDisposable
  9. {
  10. /// <summary>
  11. /// Initializes a new instance of the <see cref="ApplicationPopover"/> class.
  12. /// </summary>
  13. public ApplicationPopover () { }
  14. private readonly List<IPopover> _popovers = [];
  15. /// <summary>
  16. /// Gets the list of popovers registered with the application.
  17. /// </summary>
  18. public IReadOnlyCollection<IPopover> Popovers => _popovers.AsReadOnly ();
  19. /// <summary>
  20. /// Registers <paramref name="popover"/> with the application.
  21. /// This enables the popover to receive keyboard events even when it is not active.
  22. /// </summary>
  23. /// <remarks>
  24. /// When a popover is registered, the View instance lifetime is managed by the application. Call
  25. /// <see cref="DeRegister"/>
  26. /// to manage the lifetime of the popover directly.
  27. /// </remarks>
  28. /// <param name="popover"></param>
  29. /// <returns><paramref name="popover"/>, after it has been registered.</returns>
  30. public IPopover? Register (IPopover? popover)
  31. {
  32. if (popover is { } && !_popovers.Contains (popover))
  33. {
  34. // When created, set IPopover.Toplevel to the current Application.Top
  35. popover.Toplevel ??= Application.Top;
  36. _popovers.Add (popover);
  37. }
  38. return popover;
  39. }
  40. /// <summary>
  41. /// De-registers <paramref name="popover"/> with the application. Use this to remove the popover and it's
  42. /// keyboard bindings from the application.
  43. /// </summary>
  44. /// <remarks>
  45. /// When a popover is registered, the View instance lifetime is managed by the application. Call
  46. /// <see cref="DeRegister"/>
  47. /// to manage the lifetime of the popover directly.
  48. /// </remarks>
  49. /// <param name="popover"></param>
  50. /// <returns></returns>
  51. public bool DeRegister (IPopover? popover)
  52. {
  53. if (popover is null || !_popovers.Contains (popover))
  54. {
  55. return false;
  56. }
  57. if (GetActivePopover () == popover)
  58. {
  59. _activePopover = null;
  60. }
  61. _popovers.Remove (popover);
  62. return true;
  63. }
  64. private IPopover? _activePopover;
  65. /// <summary>
  66. /// Gets the active popover, if any.
  67. /// </summary>
  68. /// <remarks>
  69. /// Note, the active pop over does not necessarily to be registered with the application.
  70. /// </remarks>
  71. /// <returns></returns>
  72. public IPopover? GetActivePopover () { return _activePopover; }
  73. /// <summary>
  74. /// Shows <paramref name="popover"/>. IPopover implementations should use OnVisibleChanaged/VisibleChanged to be
  75. /// notified when the user has done something to cause the popover to be hidden.
  76. /// </summary>
  77. /// <remarks>
  78. /// <para>
  79. /// This API calls <see cref="Register"/>. To disable the popover from processing keyboard events,
  80. /// either call <see cref="DeRegister"/> to
  81. /// remove the popover from the application or set <see cref="View.Enabled"/> to <see langword="false"/>.
  82. /// </para>
  83. /// </remarks>
  84. /// <param name="popover"></param>
  85. public void Show (IPopover? popover)
  86. {
  87. // If there's an existing popover, hide it.
  88. if (_activePopover is View popoverView)
  89. {
  90. popoverView.Visible = false;
  91. _activePopover = null;
  92. }
  93. if (popover is View newPopover)
  94. {
  95. if (!(newPopover.ViewportSettings.HasFlag (ViewportSettingsFlags.Transparent) &&
  96. newPopover.ViewportSettings.HasFlag (ViewportSettingsFlags.TransparentMouse)))
  97. {
  98. throw new InvalidOperationException ("Popovers must have ViewportSettings.Transparent and ViewportSettings.TransparentMouse set.");
  99. }
  100. if (newPopover.KeyBindings.GetFirstFromCommands (Command.Quit) is null)
  101. {
  102. throw new InvalidOperationException ("Popovers must have a key binding for Command.Quit.");
  103. }
  104. Register (popover);
  105. if (!newPopover.IsInitialized)
  106. {
  107. newPopover.BeginInit ();
  108. newPopover.EndInit ();
  109. }
  110. _activePopover = newPopover as IPopover;
  111. newPopover.Enabled = true;
  112. newPopover.Visible = true;
  113. }
  114. }
  115. /// <summary>
  116. /// Causes the specified popover to be hidden.
  117. /// If the popover is derived from <see cref="PopoverBaseImpl"/>, this is the same as setting
  118. /// <see cref="View.Visible"/> to <see langword="false"/>.
  119. /// </summary>
  120. /// <param name="popover"></param>
  121. public void Hide (IPopover? popover)
  122. {
  123. // If there's an existing popover, hide it.
  124. if (_activePopover is View popoverView && popoverView == popover)
  125. {
  126. _activePopover = null;
  127. popoverView.Visible = false;
  128. Application.Top?.SetNeedsDraw ();
  129. }
  130. }
  131. /// <summary>
  132. /// Hides a popover view if it supports the quit command and is currently visible. It checks for the command's
  133. /// support before hiding.
  134. /// </summary>
  135. /// <param name="visiblePopover">The view that is being checked and potentially hidden based on its visibility and command support.</param>
  136. internal static void HideWithQuitCommand (View visiblePopover)
  137. {
  138. if (visiblePopover.Visible
  139. && (!visiblePopover.GetSupportedCommands ().Contains (Command.Quit)
  140. || (visiblePopover.InvokeCommand (Command.Quit) is true && visiblePopover.Visible)))
  141. {
  142. visiblePopover.Visible = false;
  143. }
  144. }
  145. /// <summary>
  146. /// Called when the user presses a key. Dispatches the key to the active popover, if any,
  147. /// otherwise to the popovers in the order they were registered. Inactive popovers only get hotkeys.
  148. /// </summary>
  149. /// <param name="key"></param>
  150. /// <returns></returns>
  151. internal bool DispatchKeyDown (Key key)
  152. {
  153. // Do active first - Active gets all key down events.
  154. var activePopover = GetActivePopover () as View;
  155. if (activePopover is { Visible: true })
  156. {
  157. //Logging.Debug ($"Active - Calling NewKeyDownEvent ({key}) on {activePopover.Title}");
  158. if (activePopover.NewKeyDownEvent (key))
  159. {
  160. return true;
  161. }
  162. }
  163. // If the active popover didn't handle the key, try the inactive ones.
  164. // Inactive only get hotkeys
  165. bool? hotKeyHandled = null;
  166. foreach (IPopover popover in _popovers)
  167. {
  168. if (popover == activePopover
  169. || popover is not View popoverView
  170. || (popover.Toplevel is { } && popover.Toplevel != Application.Top))
  171. {
  172. continue;
  173. }
  174. // hotKeyHandled = popoverView.InvokeCommandsBoundToHotKey (key);
  175. //Logging.Debug ($"Inactive - Calling NewKeyDownEvent ({key}) on {popoverView.Title}");
  176. hotKeyHandled = popoverView.NewKeyDownEvent (key);
  177. if (hotKeyHandled is true)
  178. {
  179. return true;
  180. }
  181. }
  182. return hotKeyHandled is true;
  183. }
  184. /// <inheritdoc/>
  185. public void Dispose ()
  186. {
  187. foreach (IPopover popover in _popovers)
  188. {
  189. if (popover is View view)
  190. {
  191. view.Dispose ();
  192. }
  193. }
  194. _popovers.Clear ();
  195. }
  196. }