#nullable enable using System.Diagnostics; namespace Terminal.Gui.App; /// /// 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)) { // When created, set IPopover.Toplevel to the current Application.Top popover.Toplevel ??= Application.Top; _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 null || !_popovers.Contains (popover)) { return false; } if (GetActivePopover () == popover) { _activePopover = null; } _popovers.Remove (popover); return true; } 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) { if (!(newPopover.ViewportSettings.HasFlag (ViewportSettingsFlags.Transparent) && newPopover.ViewportSettings.HasFlag (ViewportSettingsFlags.TransparentMouse))) { throw new InvalidOperationException ("Popovers must have ViewportSettings.Transparent and ViewportSettings.TransparentMouse set."); } if (newPopover.KeyBindings.GetFirstFromCommands (Command.Quit) is null) { throw new InvalidOperationException ("Popovers must have a key binding for Command.Quit."); } 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 derived 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 (); } } /// /// Hides a popover view if it supports the quit command and is currently visible. It checks for the command's /// support before hiding. /// /// The view that is being checked and potentially hidden based on its visibility and command support. internal static void HideWithQuitCommand (View visiblePopover) { if (visiblePopover.Visible && (!visiblePopover.GetSupportedCommands ().Contains (Command.Quit) || (visiblePopover.InvokeCommand (Command.Quit) is true && visiblePopover.Visible))) { visiblePopover.Visible = false; } } /// /// 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 || (popover.Toplevel is { } && popover.Toplevel != Application.Top)) { 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 (); } }