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 () { }
///
/// The instance used by this instance.
///
public IApplication? App { get; set; }
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 { } && !IsRegistered (popover))
{
// When created, set IPopover.Toplevel to the current Application.TopRunnable
popover.Current ??= App?.TopRunnable;
if (popover is View popoverView)
{
popoverView.App = App;
}
_popovers.Add (popover);
}
return popover;
}
///
/// Indicates whether a popover has been registered or not.
///
///
///
public bool IsRegistered (IPopover? popover) => popover is { } && _popovers.Contains (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 || !IsRegistered (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 (!IsRegistered (popover))
{
throw new InvalidOperationException (@"Popovers must be registered before being shown.");
}
// If there's an existing popover, hide it.
if (_activePopover is View popoverView)
{
popoverView.App = App;
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.");
}
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;
popoverView.App?.TopRunnable?.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.
View? 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.Current is { } && popover.Current != App?.TopRunnable))
{
continue;
}
// hotKeyHandled = popoverView.InvokeCommandsBoundToHotKey (key);
//Logging.Debug ($"Inactive - Calling NewKeyDownEvent ({key}) on {popoverView.Title}");
popoverView.App ??= App;
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 ();
}
}