#nullable enable namespace Terminal.Gui; /// /// Helper class for support of views for . Held by /// /// public sealed class ApplicationPopover : IDisposable { /// /// Initializes a new instance of the class. /// public ApplicationPopover () { } private readonly List _popovers = []; /// /// Gets the list of popovers registered with the application. /// public IReadOnlyCollection Popovers => _popovers.AsReadOnly (); /// /// Registers with the application. /// This enables the popover to receive keyboard events even when it is not active. /// /// /// When a popover is registered, the View instance lifetime is managed by the application. Call /// /// to manage the lifetime of the popover directly. /// /// /// , after it has been registered. public IPopover? Register (IPopover? popover) { if (popover is { } && !_popovers.Contains (popover)) { _popovers.Add (popover); } return popover; } /// /// De-registers with the application. Use this to remove the popover and it's /// keyboard bindings from the application. /// /// /// When a popover is registered, the View instance lifetime is managed by the application. Call /// /// to manage the lifetime of the popover directly. /// /// /// public bool DeRegister (IPopover? popover) { if (popover is { } && _popovers.Contains (popover)) { if (GetActivePopover () == popover) { _activePopover = null; } _popovers.Remove (popover); return true; } return false; } private IPopover? _activePopover; /// /// Gets the active popover, if any. /// /// /// Note, the active pop over does not necessarily to be registered with the application. /// /// public IPopover? GetActivePopover () { return _activePopover; } /// /// Shows . IPopover implementations should use OnVisibleChanaged/VisibleChanged to be /// notified when the user has done something to cause the popover to be hidden. /// /// /// /// This API calls . To disable the popover from processing keyboard events, /// either call to /// remove the popover from the application or set to . /// /// /// public void Show (IPopover? popover) { // If there's an existing popover, hide it. if (_activePopover is View popoverView) { popoverView.Visible = false; _activePopover = null; } if (popover is View newPopover) { Register (popover); if (!newPopover.IsInitialized) { newPopover.BeginInit (); newPopover.EndInit (); } _activePopover = newPopover as IPopover; newPopover.Enabled = true; newPopover.Visible = true; } } /// /// Causes the specified popover to be hidden. /// If the popover is dervied from , this is the same as setting /// to . /// /// public void Hide (IPopover? popover) { // If there's an existing popover, hide it. if (_activePopover is View popoverView && popoverView == popover) { _activePopover = null; popoverView.Visible = false; Application.Top?.SetNeedsDraw (); } } /// /// Called when the user presses a key. Dispatches the key to the active popover, if any, /// otherwise to the popovers in the order they were registered. Inactive popovers only get hotkeys. /// /// /// internal bool DispatchKeyDown (Key key) { // Do active first - Active gets all key down events. var activePopover = GetActivePopover () as View; if (activePopover is { Visible: true }) { Logging.Debug ($"Active - Calling NewKeyDownEvent ({key}) on {activePopover.Title}"); if (activePopover.NewKeyDownEvent (key)) { return true; } } // If the active popover didn't handle the key, try the inactive ones. // Inactive only get hotkeys bool? hotKeyHandled = null; foreach (IPopover popover in _popovers) { if (popover == activePopover || popover is not View popoverView) { continue; } // hotKeyHandled = popoverView.InvokeCommandsBoundToHotKey (key); Logging.Debug ($"Inactive - Calling NewKeyDownEvent ({key}) on {popoverView.Title}"); hotKeyHandled = popoverView.NewKeyDownEvent (key); if (hotKeyHandled is true) { return true; } } return hotKeyHandled is true; } /// public void Dispose () { foreach (IPopover popover in _popovers) { if (popover is View view) { view.Dispose (); } } _popovers.Clear (); } }