docfx/docs/Popovers.md
Popovers are transient UI elements that appear above other content to display contextual information, such as menus, tooltips, autocomplete suggestions, and dialog boxes. Terminal.Gui's popover system provides a flexible, non-modal way to present temporary UI without blocking the rest of the application.
Normally, Views cannot draw outside of their Viewport. To display content that appears to "pop over" other views, Terminal.Gui provides the popover system via Application.Popover. Popovers differ from alternatives like modifying xref:Terminal.Gui.ViewBase.Border or xref:Terminal.Gui.ViewBase.Margin behavior because they:
The popover system follows a layered architecture with well-defined responsibilities at each level:
Application.Popover (static accessor)
└── ApplicationPopover (manager)
└── IPopoverView (interface contract)
└── PopoverImpl (abstract base)
└── Popover<TView, TResult> (generic content host)
└── PopoverMenu : Popover<Menu, MenuItem> (concrete implementation)
IPopoverView — The ContractThe IPopoverView interface defines the popover contract: visibility, enabled state, anchor positioning, target reference, and MakeVisible method. It extends IPopover which provides a Current property associating the popover with a specific xref:Terminal.Gui.App.IRunnable. This association controls keyboard event scoping — if Current is null, the popover receives all keyboard events globally; if set, events only flow when the associated runnable is the active TopRunnableView.
PopoverImpl — The FoundationPopoverImpl provides the standard popover behavior that all concrete popovers inherit. It configures:
Width = Dim.Fill(), Height = Dim.Fill()ViewportSettings.Transparent | ViewportSettings.TransparentMouseApplication.GetDefaultKey (Command.Quit) to Command.QuitCommandBridge for command routingPopover<TView, TResult> — The Generic Content HostPopover<TView, TResult> extends PopoverImpl to host a typed content view and optionally extract a result when the popover closes. This layer provides:
TView that is added as a SubView, with a CommandBridge that routes Command.Activate from the content view to the popoverResultExtractor function or automatic IValue<TResult> extractionView.Visible with VisibleChanging/VisibleChanged CWP events for lifecycle managementMakeVisible / SetPositionCustom popovers that host a specific view type should extend Popover<TView, TResult> rather than PopoverImpl directly.
xref:Terminal.Gui.App.ApplicationPopover is a singleton held by IApplication.Popover that manages the lifecycle of all popovers. It maintains a list of registered popovers and tracks the single active (visible) popover. Only one popover can be active at a time — showing a new one automatically hides the previous one.
Popovers use a "full-screen transparent overlay" technique. Instead of drawing a small popup widget directly, a popover fills the entire screen with Dim.Fill() but sets its viewport to be transparent. This means:
TransparentMouse)This approach is elegant because the framework's existing transparency system handles the overlay logic. The popover doesn't need to know what's behind it or where it is positioned relative to other views.
The easiest way to create a popover is to use xref:Terminal.Gui.Views.PopoverMenu, which provides a cascading menu implementation:
// Create a popover menu with menu items
PopoverMenu contextMenu = new ([
new MenuItem ("Cut", Command.Cut),
new MenuItem ("Copy", Command.Copy),
new MenuItem ("Paste", Command.Paste),
new MenuItem ("Select All", Command.SelectAll)
]);
// IMPORTANT: Register before showing
Application.Popover?.Register (contextMenu);
// Show at mouse position or specific location
contextMenu.MakeVisible (); // Uses current mouse position
// OR
contextMenu.MakeVisible (new Point (10, 5)); // Specific location
For popovers that host a specific view type, extend Popover<TView, TResult>:
public class MyListPopover : Popover<ListView, string?>
{
public MyListPopover ()
{
// ContentView is automatically created (new ListView())
// and added as a SubView with command bridging
// Configure the content view
ContentView!.SetSource (["Option 1", "Option 2", "Option 3"]);
// Set up result extraction
ResultExtractor = lv => lv.Source?.ToList ()?.ElementAtOrDefault (lv.SelectedItem) as string;
}
}
// Usage:
MyListPopover myPopover = new ();
Application.Popover?.Register (myPopover);
myPopover.MakeVisible (new Point (10, 5));
// After closing, check myPopover.Result for the selected item
For simpler popovers that don't need typed content hosting, inherit from PopoverImpl:
public class MyCustomPopover : PopoverImpl
{
public MyCustomPopover ()
{
// PopoverImpl already sets up required defaults:
// - ViewportSettings with Transparent and TransparentMouse flags
// - Command.Quit binding to hide the popover
// - Width/Height set to Dim.Fill()
// Add your custom content
Label label = new () { Text = "Custom Popover Content" };
Add (label);
// Optionally override size
Width = 40;
Height = 10;
}
}
// Usage:
MyCustomPopover myPopover = new ();
Application.Popover?.Register (myPopover);
Application.Popover?.Show (myPopover);
A View qualifies as a popover if it:
IPopoverView — Provides visibility, anchor, target, and MakeVisible operationsCanFocus = true to receive keyboard inputViewportSettings includes both:
ViewportSettings.Transparent — Allows content beneath to show throughViewportSettings.TransparentMouse — Mouse clicks outside SubViews pass throughApplication.GetDefaultKey (Command.Quit) to Command.Quit and sets Visible = falsePopoverImpl provides all these requirements by default.
All popovers must be registered before they can be shown. Registration and showing are intentionally separate operations:
Register()) is done once and enables the popover to participate in keyboard event routing, even when hidden. It also enrolls the popover for automatic lifecycle management.Show()) can be called many times and makes the popover visible.Attempting to call Show() on an unregistered popover throws InvalidOperationException.
PopoverMenu popover = new ([...]);
// REQUIRED: Register with the application
Application.Popover?.Register (popover);
// Now you can show it (and hide/show it repeatedly)
Application.Popover?.Show (popover);
// OR
popover.MakeVisible (); // For PopoverMenu
Why Registration is Required:
Application.Shutdown)Current runnable association automatically to TopRunnableViewShow a popover:
Application.Popover?.Show (popover);
The Show() method validates that the popover:
Transparent and TransparentMouse viewport flags setCommand.QuitIt then initializes the popover if needed, hides any previously active popover, and makes the new one visible.
Hide a popover:
// Method 1: Via ApplicationPopover
Application.Popover?.Hide (popover);
// Method 2: Set Visible property
popover.Visible = false;
// Automatic hiding occurs when:
// - User presses the quit key (typically Esc; see Application.GetDefaultKey (Command.Quit))
// - User clicks outside the popover (not on a SubView)
// - Another popover is shown
Registered popovers:
Application.Shutdown () is calledTo manage lifetime manually:
// Deregister to take ownership of disposal
Application.Popover?.DeRegister (popover);
// Now you're responsible for disposal
popover.Dispose ();
The popover lifecycle is driven entirely by the Visible property — there is no separate "Open/Close" API. When visibility changes, a cascade of events occurs:
Becoming visible (Visible changes to true):
PopoverImpl.OnVisibleChanging() calls Layout(App.Screen.Size) to size the popover to the screenPopoverMenu.OnVisibleChanged() calls Root.ShowMenu() (which sets Menu.Visible = true and Menu.Enabled = true)Popover<TView, TResult>.OnVisibleChanged() sets ContentView.Visible = trueBecoming hidden (Visible changes to false):
PopoverImpl.OnVisibleChanging() restores focus to the previously-focused view in the TopRunnableViewPopoverMenu.OnVisibleChanged() calls Root.HideMenu() and ApplicationPopover.Hide()Popover<TView, TResult>.OnVisibleChanged() sets ContentView.Visible = false and extracts ResultApplicationPopover.Hide() clears the active popover reference and triggers a redrawThis pattern means setting Visible = false is equivalent to calling Hide() — both produce the same result.
When a key is pressed, ApplicationPopover.DispatchKeyDown() routes it through popovers in a specific order:
Current runnable association. Popovers whose Current doesn't match the active TopRunnableView are skipped.This design ensures:
Registered popovers receive keyboard events even when not visible, enabling global hotkey support:
PopoverMenu menu = new ([...]);
menu.Key = Key.F10.WithShift; // Default hotkey
Application.Popover?.Register (menu);
// Now pressing Shift+F10 anywhere in the app will show the menu
The IPopover.Current property associates a popover with a specific xref:Terminal.Gui.App.IRunnable:
null: Popover receives all keyboard events from the applicationApplication.TopRunnableView during registration// Associate with a specific runnable
myPopover.Current = myWindow; // Only active when myWindow is the top runnable
When visible:
TransparentMouse)When hidden:
PopoverImpl sets Width = Dim.Fill () and Height = Dim.Fill () (see xref:Terminal.Gui.ViewBase.Dim), making the popover fill the screen by default. The transparent viewport settings allow content beneath to remain visible.
Override Width and Height to customize size:
public class MyPopover : PopoverImpl
{
public MyPopover ()
{
Width = 40; // Fixed width
Height = Dim.Auto (); // Auto height based on content
}
}
xref:Terminal.Gui.Views.PopoverMenu provides positioning helpers:
// Position at specific screen coordinates
menu.SetPosition (new Point (10, 5));
// Show and position in one call
menu.MakeVisible (new Point (10, 5));
// Uses mouse position if null
menu.MakeVisible (); // Uses Application.Mouse.LastMousePosition
The menu automatically adjusts position to ensure it remains fully visible on screen.
xref:Terminal.Gui.Views.PopoverMenu extends Popover<Menu, MenuItem> and is a sophisticated cascading menu implementation used for:
Key Features:
Popover<Menu, MenuItem>, inheriting ContentView, MakeVisible, SetPosition, Result, and AnchorRoot property aliases ContentView — the hosted xref:Terminal.Gui.Views.Menu instanceCommandBridge instances route commands across containment boundaries: Content bridge (Menu → PopoverMenu) and Target bridge (PopoverMenu → host/MenuBarItem)new Line ()Example with submenus:
PopoverMenu fileMenu = new ([
new MenuItem ("New", Command.New),
new MenuItem ("Open", Command.Open),
new MenuItem {
Title = "Recent",
SubMenu = new Menu ([
new MenuItem ("File1.txt", Command.Open),
new MenuItem ("File2.txt", Command.Open)
])
},
new Line (),
new MenuItem ("Exit", Command.Quit)
]);
Application.Popover?.Register (fileMenu);
fileMenu.MakeVisible ();
Popovers use ViewportSettings.TransparentMouse, which means:
This creates the expected behavior where clicking outside a menu or dialog closes it.
When a popover is dismissed by a mouse press outside its SubViews, ApplicationMouse.RaiseMouseEvent recurses so the click event can reach views beneath the popover. This enables the "click a different button while a context menu is open" scenario — the menu closes and the button activates in a single click.
However, if the view beneath the popover is the same view that opened it (e.g., a DropDownList toggle button or a MenuBarItem), the recursed event would re-activate the view and re-show the popover. To prevent this, ApplicationMouse records which popover was just dismissed (DismissedByMousePress) and ApplicationPopover.Show checks this guard — silently returning if the caller is trying to re-show the same popover. The guard spans the entire mouse interaction cycle (press → release → click) because Button and other views with ShouldAutoGrab start an auto-grab on the pressed event and invoke commands on the subsequent clicked event. The guard is cleared when:
IsSingleDoubleOrTripleClicked)ApplicationMouse.ResetState is calledThis means views that open popovers on activation do not need their own re-show prevention logic — the framework handles it automatically.
Always Register First
// WRONG - Will throw InvalidOperationException
PopoverMenu menu = new ([...]);
menu.MakeVisible ();
// CORRECT
PopoverMenu menu = new ([...]);
Application.Popover?.Register (menu);
menu.MakeVisible ();
Use PopoverMenu for Menus
Manage Lifecycle Appropriately
Test Global Hotkeys
Handle Edge Cases
PopoverMenu contextMenu = new ([...]);
contextMenu.MouseFlags = MouseFlags.Button3Clicked; // Right-click
Application.Popover?.Register (contextMenu);
myView.MouseClick += (s, e) =>
{
if (e.MouseEvent.Flags == MouseFlags.Button3Clicked)
{
contextMenu.MakeVisible (myView.ScreenToViewport (e.MouseEvent.Position));
e.Handled = true;
}
};
public class AutocompletePopover : PopoverImpl
{
private ListView _listView;
public AutocompletePopover ()
{
Width = 30;
Height = 10;
_listView = new ListView
{
Width = Dim.Fill (),
Height = Dim.Fill ()
};
Add (_listView);
}
public void ShowSuggestions (IEnumerable<string> suggestions, Point position)
{
_listView.SetSource (suggestions.ToList ());
// Position below the text entry field
X = position.X;
Y = position.Y + 1;
Visible = true;
}
}
PopoverMenu commandPalette = new (GetAllCommands ());
commandPalette.Key = Key.P.WithCtrl; // Ctrl+P to show
Application.Popover?.Register (commandPalette);
// Now Ctrl+P anywhere in the app shows the command palette
IPopoverView - Interface for popover views (extends IPopover)PopoverImpl - Abstract base class providing standard popover behavior (implements IPopoverView)Popover<TView, TResult> - Generic base class for popovers hosting typed content views with result extractionPopover<Menu, MenuItem>)TextField with Popover<ListView, string?>)Application.Popover)