ApplicationPopover.cs 6.5 KB

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