Popovers Deep Dive
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.
Overview
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 Popover. Popovers differ from alternatives like modifying Border or Margin behavior because they:
- Are managed centrally by the application
- Support focus and keyboard event routing
- Automatically hide in response to user actions
- Can receive global hotkeys even when not visible
Creating a Popover
Using PopoverMenu
The easiest way to create a popover is to use 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
Creating a Custom Popover
To create a custom popover, inherit from PopoverBaseImpl:
public class MyCustomPopover : PopoverBaseImpl
{
public MyCustomPopover ()
{
// PopoverBaseImpl 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);
Popover Requirements
A View qualifies as a popover if it:
- **Implements @Terminal.Gui.App.IPopover** - Provides the
Currentproperty for runnable association - Is Focusable -
CanFocus = trueto receive keyboard input - Is Transparent -
ViewportSettingsincludes both:ViewportSettings.Transparent- Allows content beneath to show throughViewportSettings.TransparentMouse- Mouse clicks outside subviews pass through
- Handles Quit - Binds
Application.QuitKeytoCommand.Quitand setsVisible = false
PopoverBaseImpl provides all these requirements by default.
Registration and Lifecycle
Registration (REQUIRED)
All popovers must be registered before they can be shown:
PopoverMenu popover = new ([...]);
// REQUIRED: Register with the application
Application.Popover?.Register (popover);
// Now you can show it
Application.Popover?.Show (popover);
// OR
popover.MakeVisible (); // For PopoverMenu
Why Registration is Required:
- Enables keyboard event routing to the popover
- Allows global hotkeys to work even when popover is hidden
- Manages popover lifecycle and disposal
Showing and Hiding
Show a popover:
Application.Popover?.Show (popover);
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 Application.QuitKey (typically Esc)
// - User clicks outside the popover (not on a subview)
// - Another popover is shown
Lifecycle Management
Registered popovers:
- Have their lifetime managed by the application
- Are automatically disposed when
Application.Shutdown ()is called - Receive keyboard events based on their associated runnable
To manage lifetime manually:
// Deregister to take ownership of disposal
Application.Popover?.DeRegister (popover);
// Now you're responsible for disposal
popover.Dispose ();
Keyboard Event Routing
Global Hotkeys
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
Runnable Association
The Current property associates a popover with a specific @Terminal.Gui.IRunnable:
- If
null: Popover receives all keyboard events from the application - If set: Popover only receives events when the associated runnable is active
- Automatically set to
Application.TopRunnableViewduring registration
// Associate with a specific runnable
myPopover.Current = myWindow; // Only active when myWindow is the top runnable
Focus and Input
When visible:
- Popovers receive focus automatically
- All keyboard input goes to the popover until hidden
- Mouse clicks on subviews are captured
- Mouse clicks outside subviews pass through (due to
TransparentMouse)
When hidden:
- Only registered hotkeys are processed
- Other keyboard input is not captured
Layout and Positioning
Default Layout
PopoverBaseImpl sets Width = Dim.Fill () and Height = Dim.Fill (), making the popover fill the screen by default. The transparent viewport settings allow content beneath to remain visible.
Custom Sizing
Override Width and Height to customize size:
public class MyPopover : PopoverBaseImpl
{
public MyPopover ()
{
Width = 40; // Fixed width
Height = Dim.Auto (); // Auto height based on content
}
}
Positioning with PopoverMenu
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.
Built-in Popover Types
PopoverMenu
PopoverMenu is a sophisticated cascading menu implementation used for:
- Context menus
- @Terminal.Gui.MenuBar drop-down menus
- Custom menu scenarios
Key Features:
- Cascading submenus with automatic positioning
- Keyboard navigation (arrow keys, hotkeys)
- Automatic key binding from Commands
- Mouse support
- Separator lines via
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 ();
Mouse Event Handling
Popovers use ViewportSettings.TransparentMouse, which means:
- Clicks on popover subviews: Captured and handled normally
- Clicks outside subviews: Pass through to views beneath
- Clicks on background: Automatically hide the popover
This creates the expected behavior where clicking outside a menu or dialog closes it.
Best Practices
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
- Don't reinvent the wheel for standard menu scenarios
- Leverage built-in keyboard navigation and positioning
Manage Lifecycle Appropriately
- Let the application manage disposal for long-lived popovers
- Deregister and manually dispose short-lived or conditional popovers
Test Global Hotkeys
- Ensure hotkeys don't conflict with application-level keys
- Consider providing configuration for custom hotkeys
Handle Edge Cases
- Test positioning near screen edges
- Verify behavior with multiple runnables
- Test with keyboard-only navigation
Common Scenarios
Context Menu on Right-Click
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;
}
};
Autocomplete Popup
public class AutocompletePopover : PopoverBaseImpl
{
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;
}
}
Global Command Palette
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
API Reference
- IPopover - Interface for popover views
- PopoverBaseImpl - Abstract base class for custom popovers
- PopoverMenu - Cascading menu implementation
- ApplicationPopover - Popover manager (accessed via
Application.Popover)
See Also
- Keyboard Deep Dive - Understanding keyboard event routing
- Mouse Deep Dive - Mouse event handling
- MenuBar Overview - Using PopoverMenu with MenuBar