diff --git a/Examples/UICatalog/Scenarios/Menus.cs b/Examples/UICatalog/Scenarios/Menus.cs index 4796473ac..f044749ba 100644 --- a/Examples/UICatalog/Scenarios/Menus.cs +++ b/Examples/UICatalog/Scenarios/Menus.cs @@ -347,6 +347,8 @@ public class Menus : Scenario }; ContextMenu.EnableForDesign (ref host); + Application.Popover.Register (ContextMenu); + ContextMenu.Visible = false; // Demo of PopoverMenu as a context menu diff --git a/Terminal.Gui/App/IPopover.cs b/Terminal.Gui/App/IPopover.cs index be577b871..00ff8087b 100644 --- a/Terminal.Gui/App/IPopover.cs +++ b/Terminal.Gui/App/IPopover.cs @@ -8,54 +8,64 @@ namespace Terminal.Gui.App; /// /// A popover is a transient UI element that appears above other content to display contextual information or UI, /// such as menus, tooltips, or dialogs. -/// Popovers are managed by and are typically shown using -/// . /// /// -/// Popovers are not modal; they do not block input to the rest of the application, but they do receive focus and -/// input events while visible. -/// When a popover is shown, it is responsible for handling its own layout and content. +/// IMPORTANT: Popovers must be registered with using +/// before they can be shown with . /// /// +/// Lifecycle:
+/// When registered, the popover's lifetime is managed by the application. Registered popovers are +/// automatically disposed when is called. Call +/// to manage the lifetime directly. +///
+/// +/// Visibility and Hiding:
/// Popovers are automatically hidden when: -/// -/// The user clicks outside the popover (unless occluded by a subview of the popover). -/// The user presses (typically Esc). -/// Another popover is shown. -/// ///
+/// +/// The user clicks outside the popover (unless clicking on a subview). +/// The user presses (typically Esc). +/// Another popover is shown. +/// is set to . +/// /// /// Focus and Input:
-/// When visible, a popover receives focus and input events. If the user clicks outside the popover (and not on a -/// subview), -/// presses , or another popover is shown, the popover will be hidden -/// automatically. +/// Popovers are not modal but do receive focus and input events while visible. +/// Registered popovers receive keyboard events even when not visible, enabling global hotkey support. ///
/// /// Layout:
-/// When the popover becomes visible, it is automatically laid out to fill the screen by default. You can override -/// this behavior -/// by setting and in your derived class. +/// When becoming visible, popovers are automatically laid out to fill the screen by default. +/// Override and to customize size. ///
/// -/// Mouse:
-/// Popovers are transparent to mouse events (see ), -/// meaning mouse events in a popover that are not also within a subview of the popover will not be captured. +/// Mouse Events:
+/// Popovers use , meaning mouse events +/// outside subviews are not captured. ///
/// -/// Custom Popovers:
-/// To create a custom popover, inherit from and add your own content and logic. +/// Creating Custom Popovers:
+/// Inherit from and add your own content and logic. ///
/// public interface IPopover { /// - /// Gets or sets the that this Popover is associated with. If null, it is not associated with - /// any Runnable and will receive all keyboard - /// events from the . If set, it will only receive keyboard events the Runnable would normally - /// receive. - /// When is called, the is set to the current - /// if not already set. + /// Gets or sets the that this popover is associated with. /// + /// + /// + /// If , the popover is not associated with any runnable and will receive all keyboard + /// events from the application. + /// + /// + /// If set, the popover will only receive keyboard events when the associated runnable is active. + /// + /// + /// When is called, this property is automatically set to + /// if not already set. + /// + /// IRunnable? Current { get; set; } } diff --git a/Terminal.Gui/App/PopoverBaseImpl.cs b/Terminal.Gui/App/PopoverBaseImpl.cs index ff118df35..81673e405 100644 --- a/Terminal.Gui/App/PopoverBaseImpl.cs +++ b/Terminal.Gui/App/PopoverBaseImpl.cs @@ -2,36 +2,36 @@ namespace Terminal.Gui.App; /// -/// Abstract base class for popover views in Terminal.Gui. +/// Abstract base class for popover views in Terminal.Gui. Implements . /// /// /// -/// Popover Lifecycle:
-/// To display a popover, use . To hide a popover, either call -/// , -/// set to , or show another popover. +/// IMPORTANT: Popovers must be registered with using +/// before they can be shown. ///
/// -/// Focus and Input:
-/// When visible, a popover receives focus and input events. If the user clicks outside the popover (and not on a -/// subview), -/// presses , or another popover is shown, the popover will be hidden -/// automatically. +/// Requirements:
+/// Derived classes must: ///
+/// +/// Set to include and . +/// Add a key binding for (typically bound to ). +/// /// -/// Layout:
-/// When the popover becomes visible, it is automatically laid out to fill the screen by default. You can override -/// this behavior -/// by setting and in your derived class. +/// Default Behavior:
+/// This base class provides: ///
+/// +/// Fills the screen by default ( = , = ). +/// Transparent viewport settings for proper mouse event handling. +/// Automatic layout when becoming visible. +/// Focus restoration when hidden. +/// Default implementation that hides the popover. +/// /// -/// Mouse:
-/// Popovers are transparent to mouse events (see ), -/// meaning mouse events in a popover that are not also within a subview of the popover will not be captured. -///
-/// -/// Custom Popovers:
-/// To create a custom popover, inherit from and add your own content and logic. +/// Lifecycle:
+/// Use to display and or +/// set to to hide. ///
///
public abstract class PopoverBaseImpl : View, IPopover @@ -40,7 +40,15 @@ public abstract class PopoverBaseImpl : View, IPopover /// Initializes a new instance of the class. /// /// - /// By default, the popover fills the available screen area and is focusable. + /// + /// Sets up default popover behavior: + /// + /// + /// Fills the screen ( = , = ). + /// Sets to . + /// Configures with and . + /// Adds bound to which hides the popover when invoked. + /// /// protected PopoverBaseImpl () { @@ -87,15 +95,19 @@ public abstract class PopoverBaseImpl : View, IPopover } /// - /// Called when the property is changing. + /// Called when the property is changing. Handles layout and focus management. /// - /// - /// When becoming visible, the popover is laid out to fit the screen. - /// When becoming hidden, focus is restored to the previous view. - /// /// /// to cancel the visibility change; otherwise, . /// + /// + /// + /// When becoming visible: Lays out the popover to fit the screen. + /// + /// + /// When becoming hidden: Restores focus to the previously focused view in the view hierarchy. + /// + /// protected override bool OnVisibleChanging () { bool ret = base.OnVisibleChanging (); diff --git a/Terminal.Gui/Views/Menu/PopoverMenu.cs b/Terminal.Gui/Views/Menu/PopoverMenu.cs index 7f1e45979..acd3c2bd6 100644 --- a/Terminal.Gui/Views/Menu/PopoverMenu.cs +++ b/Terminal.Gui/Views/Menu/PopoverMenu.cs @@ -1,16 +1,29 @@ - - namespace Terminal.Gui.Views; /// -/// Provides a cascading menu that pops over all other content. Can be used as a context menu or a drop-down -/// all other content. Can be used as a context menu or a drop-down -/// menu as part of as part of . +/// A -derived view that provides a cascading menu. +/// Can be used as a context menu or a drop-down menu as part of . /// /// /// -/// To use as a context menu, register the popover menu with and call -/// . +/// IMPORTANT: Must be registered with via +/// before calling or +/// . +/// +/// +/// Usage Example: +/// +/// +/// var menu = new PopoverMenu ([ +/// new MenuItem ("Cut", Command.Cut), +/// new MenuItem ("Copy", Command.Copy), +/// new MenuItem ("Paste", Command.Paste) +/// ]); +/// Application.Popover?.Register (menu); +/// menu.MakeVisible (); // or Application.Popover?.Show (menu); +/// +/// +/// See and for lifecycle, focus, and keyboard handling details. /// /// public class PopoverMenu : PopoverBaseImpl, IDesignable @@ -22,9 +35,12 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable /// /// Initializes a new instance of the class. If any of the elements of - /// is , - /// a see will be created instead. + /// is , a will be created instead. /// + /// The views to use as menu items. Null elements become separator lines. + /// + /// Remember to call before calling . + /// public PopoverMenu (IEnumerable? menuItems) : this ( new Menu (menuItems?.Select (item => item ?? new Line ())) { @@ -32,17 +48,27 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable }) { } - /// + /// + /// Initializes a new instance of the class with the specified menu items. + /// + /// The menu items to display in the popover. + /// + /// Remember to call before calling . + /// public PopoverMenu (IEnumerable? menuItems) : this ( - new Menu (menuItems) - { - Title = "Popover Root" - }) + new Menu (menuItems) + { + Title = "Popover Root" + }) { } /// /// Initializes a new instance of the class with the specified root . /// + /// The root menu that contains the top-level menu items. + /// + /// Remember to call before calling . + /// public PopoverMenu (Menu? root) { // Do this to support debugging traces where Title gets set @@ -132,7 +158,14 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable private Key _key = DefaultKey; - /// Specifies the key that will activate the context menu. + /// + /// Gets or sets the key that will activate the popover menu when it is registered but not visible. + /// + /// + /// This key binding works as a global hotkey when the popover is registered with + /// . The default value is ( with + /// Shift). + /// public Key Key { get => _key; @@ -144,10 +177,17 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable } } - /// Raised when is changed. + /// + /// Raised when the property is changed. + /// public event EventHandler? KeyChanged; - /// The default key for activating popover menus. + /// + /// Gets or sets the default key for activating popover menus. The default value is with Shift. + /// + /// + /// This is a configuration property that affects all new instances. + /// [ConfigurationProperty (Scope = typeof (SettingsScope))] public static Key DefaultKey { get; set; } = Key.F10.WithShift; @@ -159,12 +199,25 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable /// /// Makes the popover menu visible and locates it at . The actual position of the - /// menu - /// will be adjusted to - /// ensure the menu fully fits on the screen, and the mouse cursor is over the first cell of the - /// first MenuItem. + /// menu will be adjusted to ensure the menu fully fits on the screen, with the mouse cursor positioned over + /// the first cell of the first . /// - /// If , the current mouse position will be used. + /// + /// The ideal screen-relative position for the menu. If , the current mouse position will be + /// used. + /// + /// + /// + /// IMPORTANT: The popover must be registered with before calling this + /// method. + /// Call first. + /// + /// + /// This method internally calls , which will throw + /// if the popover is not registered. + /// + /// + /// Thrown if the popover has not been registered. public void MakeVisible (Point? idealScreenPosition = null) { if (Visible) @@ -180,12 +233,18 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable } /// - /// Locates the popover menu at . The actual position of the menu will be - /// adjusted to - /// ensure the menu fully fits on the screen, and the mouse cursor is over the first cell of the - /// first MenuItem (if possible). + /// Sets the position of the popover menu at . The actual position will be + /// adjusted to ensure the menu fully fits on the screen, with the mouse cursor positioned over the first cell of + /// the first (if possible). /// - /// If , the current mouse position will be used. + /// + /// The ideal screen-relative position for the menu. If , the current mouse position will be + /// used. + /// + /// + /// This method only sets the position; it does not make the popover visible. Use to + /// both position and show the popover. + /// public void SetPosition (Point? idealScreenPosition = null) { idealScreenPosition ??= App?.Mouse.LastMousePosition; @@ -212,6 +271,10 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable } /// + /// + /// When becoming visible, the root menu is added and shown. When becoming hidden, the root menu is removed + /// and the popover is hidden via . + /// protected override void OnVisibleChanged () { // Logging.Debug ($"{Title} - Visible: {Visible}"); @@ -231,8 +294,17 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable private Menu? _root; /// - /// Gets or sets the that is the root of the Popover Menu. + /// Gets or sets the that is the root of the popover menu hierarchy. /// + /// + /// + /// The root menu contains the top-level menu items. Setting this property updates key bindings and + /// event subscriptions for all menus in the hierarchy. + /// + /// + /// When set, all submenus are configured with appropriate event handlers for selection and acceptance. + /// + /// public Menu? Root { get => _root; @@ -306,6 +378,10 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable } /// + /// + /// This method checks all menu items in the hierarchy for a matching key binding and invokes the + /// appropriate menu item if found. + /// protected override bool OnKeyDownNotHandled (Key key) { // See if any of our MenuItems have this key as Key @@ -325,9 +401,12 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable } /// - /// Gets all the submenus in the PopoverMenu. + /// Gets all the submenus in the popover menu hierarchy, including the root menu. /// - /// + /// An enumerable collection of all instances in the hierarchy. + /// + /// This method performs a depth-first traversal of the menu tree, starting from . + /// public IEnumerable GetAllSubMenus () { List result = []; @@ -358,9 +437,12 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable } /// - /// Gets all the MenuItems in the PopoverMenu. + /// Gets all the menu items in the popover menu hierarchy. /// - /// + /// An enumerable collection of all instances across all menus in the hierarchy. + /// + /// This method traverses all menus returned by and collects their menu items. + /// internal IEnumerable GetMenuItemsOfAllSubMenus () { List result = []; @@ -380,9 +462,17 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable } /// - /// Pops up the submenu of the specified MenuItem, if there is one. + /// Shows the submenu of the specified , if it has one. /// - /// + /// The menu item whose submenu should be shown. + /// + /// + /// If another submenu is currently visible at the same level, it will be hidden before showing the new one. + /// + /// + /// The submenu is positioned to the right of the menu item, adjusted to ensure full visibility on screen. + /// + /// internal void ShowSubMenu (MenuItem? menuItem) { var menu = menuItem?.SuperView as Menu; @@ -416,11 +506,14 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable } /// - /// Gets the most visible screen-relative location for . + /// Calculates the most visible screen-relative location for the specified . /// - /// The menu to locate. - /// Ideal screen-relative location. - /// + /// The menu to position. + /// The ideal screen-relative location. + /// The adjusted screen-relative position that ensures maximum visibility of the menu. + /// + /// This method adjusts the position to keep the menu fully visible on screen, considering screen boundaries. + /// internal Point GetMostVisibleLocationForSubMenu (Menu menu, Point idealLocation) { var pos = Point.Empty; @@ -489,6 +582,7 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable private void MenuOnAccepting (object? sender, CommandEventArgs e) { var senderView = sender as View; + // Logging.Debug ($"{Title} ({e.Context?.Source?.Title}) Command: {e.Context?.Command} - Sender: {senderView?.GetType ().Name}"); if (e.Context?.Command != Command.HotKey) @@ -524,6 +618,14 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable } /// + /// + /// + /// When the popover is not visible, only hotkey commands are processed. + /// + /// + /// This method raises for commands that originate from menu items in the hierarchy. + /// + /// protected override bool OnAccepting (CommandEventArgs args) { // Logging.Debug ($"{Title} ({args.Context?.Source?.Title}) Command: {args.Context?.Command}"); @@ -560,8 +662,6 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable return false; } - - private void MenuOnSelectedMenuItemChanged (object? sender, MenuItem? e) { // Logging.Debug ($"{Title} - e.Title: {e?.Title}"); @@ -569,6 +669,13 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable } /// + /// + /// Thrown if attempting to add a or directly to the popover. + /// + /// + /// Do not add or views directly to the popover. + /// Use the property instead. + /// protected override void OnSubViewAdded (View view) { if (Root is null && (view is Menu || view is MenuItem)) @@ -580,6 +687,9 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable } /// + /// + /// This method unsubscribes from all menu events and disposes the root menu. + /// protected override void Dispose (bool disposing) { if (disposing) @@ -600,7 +710,16 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable base.Dispose (disposing); } - /// + /// + /// Enables the popover menu for use in design-time scenarios. + /// + /// The type of the target view context. + /// The target view to associate with the menu commands. + /// if successfully enabled for design; otherwise, . + /// + /// This method creates a default set of menu items (Cut, Copy, Paste, Select All, Quit) for design-time use. + /// It is primarily used for demonstration and testing purposes. + /// public bool EnableForDesign (ref TContext targetView) where TContext : notnull { // Note: This menu is used by unit tests. If you modify it, you'll likely have to update diff --git a/docfx/docs/Popovers.md b/docfx/docs/Popovers.md index 54cc24d10..c1569ac11 100644 --- a/docfx/docs/Popovers.md +++ b/docfx/docs/Popovers.md @@ -1,18 +1,368 @@ # Popovers Deep Dive -Normally Views cannot draw outside of their `Viewport`. Options for influencing content outside of the `Viewport` include: +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. -1) Modifying the `Border` behavior -2) Modifying the `Margin` behavior -3) Using @Terminal.Gui.App.Application.Popover +## Overview -Popovers are useful for scenarios such as menus, autocomplete popups, and drop-down combo boxes. +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 @Terminal.Gui.App.Application.Popover. Popovers differ from alternatives like modifying `Border` or `Margin` behavior because they: -A `Popover` is any View that meets these characteristics: +- 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 -- Implements the @Terminal.Gui.App.IPopover interface -- Is Focusable (`CetFocus = true`) -- Is Transparent (`ViewportSettings = ViewportSettings.Transparent | ViewportSettings.TransparentMouse` -- Sets `Visible = false` when it receives `Application.QuitKey` +## Creating a Popover -@Terminal.Gui.Views.PopoverMenu provides a sophisticated implementation that can be used as a context menu and is the basis for @Terminal.Gui.MenuBar. \ No newline at end of file +### Using PopoverMenu + +The easiest way to create a popover is to use @Terminal.Gui.Views.PopoverMenu, which provides a cascading menu implementation: + +```csharp +// 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 @Terminal.Gui.App.PopoverBaseImpl: + +```csharp +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: + +1. **Implements @Terminal.Gui.App.IPopover** - Provides the `Current` property for runnable association +2. **Is Focusable** - `CanFocus = true` to receive keyboard input +3. **Is Transparent** - `ViewportSettings` includes both: + - `ViewportSettings.Transparent` - Allows content beneath to show through + - `ViewportSettings.TransparentMouse` - Mouse clicks outside subviews pass through +4. **Handles Quit** - Binds `Application.QuitKey` to `Command.Quit` and sets `Visible = false` + +@Terminal.Gui.App.PopoverBaseImpl provides all these requirements by default. + +## Registration and Lifecycle + +### Registration (REQUIRED) + +**All popovers must be registered before they can be shown:** + +```csharp +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:** +```csharp +Application.Popover?.Show (popover); +``` + +**Hide a popover:** +```csharp +// 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:** +```csharp +// 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: + +```csharp +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 @Terminal.Gui.App.IPopover.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.TopRunnableView` during registration + +```csharp +// 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 + +@Terminal.Gui.App.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: + +```csharp +public class MyPopover : PopoverBaseImpl +{ + public MyPopover () + { + Width = 40; // Fixed width + Height = Dim.Auto (); // Auto height based on content + } +} +``` + +### Positioning with PopoverMenu + +@Terminal.Gui.Views.PopoverMenu provides positioning helpers: + +```csharp +// 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 + +@Terminal.Gui.Views.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:** +```csharp +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 + +1. **Always Register First** + ```csharp + // WRONG - Will throw InvalidOperationException + PopoverMenu menu = new ([...]); + menu.MakeVisible (); + + // CORRECT + PopoverMenu menu = new ([...]); + Application.Popover?.Register (menu); + menu.MakeVisible (); + ``` + +2. **Use PopoverMenu for Menus** + - Don't reinvent the wheel for standard menu scenarios + - Leverage built-in keyboard navigation and positioning + +3. **Manage Lifecycle Appropriately** + - Let the application manage disposal for long-lived popovers + - Deregister and manually dispose short-lived or conditional popovers + +4. **Test Global Hotkeys** + - Ensure hotkeys don't conflict with application-level keys + - Consider providing configuration for custom hotkeys + +5. **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 + +```csharp +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 + +```csharp +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 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 + +```csharp +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 + +- @Terminal.Gui.App.IPopover - Interface for popover views +- @Terminal.Gui.App.PopoverBaseImpl - Abstract base class for custom popovers +- @Terminal.Gui.Views.PopoverMenu - Cascading menu implementation +- @Terminal.Gui.App.ApplicationPopover - Popover manager (accessed via `Application.Popover`) + +## See Also + +- [Keyboard Deep Dive](keyboard.md) - Understanding keyboard event routing +- [Mouse Deep Dive](mouse.md) - Mouse event handling +- [MenuBar Overview](menubar.md) - Using PopoverMenu with MenuBar \ No newline at end of file