From 4dc193b6a32f34197860c1d3d8007d95a60cc8e8 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 16 Dec 2025 13:30:58 -0700 Subject: [PATCH] Expand and clarify popover XML documentation Significantly improve and expand XML documentation for IPopover, PopoverBaseImpl, and PopoverMenu. Add detailed remarks on popover lifecycle, registration requirements, focus, input, and mouse handling. Provide usage examples, parameter and exception documentation, and clarify method/property summaries. Warn about the need to register popovers before showing. No functional code changes except for minor summary tweaks for clarity. --- Examples/UICatalog/Scenarios/Menus.cs | 2 + Terminal.Gui/App/IPopover.cs | 66 ++++---- Terminal.Gui/App/PopoverBaseImpl.cs | 66 ++++---- Terminal.Gui/Views/Menu/PopoverMenu.cs | 201 ++++++++++++++++++++----- 4 files changed, 239 insertions(+), 96 deletions(-) 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