diff --git a/Examples/PopoverWrapperExample/PopoverWrapperExample.csproj b/Examples/PopoverWrapperExample/PopoverWrapperExample.csproj
new file mode 100644
index 000000000..7e34acedb
--- /dev/null
+++ b/Examples/PopoverWrapperExample/PopoverWrapperExample.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+ latest
+
+
+
+
+
+
+
diff --git a/Examples/PopoverWrapperExample/Program.cs b/Examples/PopoverWrapperExample/Program.cs
new file mode 100644
index 000000000..86a1fd873
--- /dev/null
+++ b/Examples/PopoverWrapperExample/Program.cs
@@ -0,0 +1,417 @@
+// Example demonstrating how to make ANY View into a popover without implementing IPopover
+
+using Terminal.Gui;
+using Terminal.Gui.App;
+using Terminal.Gui.Configuration;
+using Terminal.Gui.Drawing;
+using Terminal.Gui.ViewBase;
+using Terminal.Gui.Views;
+using Attribute = Terminal.Gui.Drawing.Attribute;
+
+IApplication app = Application.Create ();
+app.Init ();
+
+// Create a main window with some buttons to trigger popovers
+Window mainWindow = new ()
+{
+ Title = "PopoverWrapper Example - Press buttons to show popovers",
+ X = 0,
+ Y = 0,
+ Width = Dim.Fill (),
+ Height = Dim.Fill ()
+};
+
+Label label = new ()
+{
+ Text = "Click buttons below or press their hotkeys to show different popovers.\nPress Esc to close a popover.",
+ X = Pos.Center (),
+ Y = 1,
+ Width = Dim.Fill (),
+ Height = 2,
+ TextAlignment = Alignment.Center
+};
+
+mainWindow.Add (label);
+
+// Example 1: Simple view as popover
+Button button1 = new ()
+{
+ Title = "_1: Simple View Popover",
+ X = Pos.Center (),
+ Y = Pos.Top (label) + 3
+};
+
+button1.Accepting += (s, e) =>
+{
+ IApplication? application = (s as View)?.App;
+
+ if (application is null)
+ {
+ return;
+ }
+
+ View simpleView = new ()
+ {
+ Title = "Simple Popover",
+ Width = Dim.Auto (),
+ Height = Dim.Auto (),
+ SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Menu)
+ };
+
+ simpleView.Add (
+ new Label
+ {
+ Text = "This is a simple View wrapped as a popover!\n\nPress Esc or click outside to dismiss.",
+ X = Pos.Center (),
+ Y = Pos.Center (),
+ TextAlignment = Alignment.Center
+ });
+
+ PopoverWrapper popover = simpleView.AsPopover ();
+ popover.X = Pos.Center ();
+ popover.Y = Pos.Center ();
+ application.Popover?.Register (popover);
+ application.Popover?.Show (popover);
+
+ e.Handled = true;
+};
+
+mainWindow.Add (button1);
+
+// Example 2: ListView as popover
+Button button2 = new ()
+{
+ Title = "_2: ListView Popover",
+ X = Pos.Center (),
+ Y = Pos.Bottom (button1) + 1
+};
+
+ListView listView = new ()
+{
+ Title = "Select an Item",
+ X = Pos.Center (),
+ Y = Pos.Center (),
+ Width = Dim.Percent (30),
+ Height = Dim.Percent (40),
+ BorderStyle = LineStyle.Single,
+ Source = new ListWrapper (["Apple", "Banana", "Cherry", "Date", "Elderberry", "Fig", "Grape"]),
+ SelectedItem = 0
+};
+
+PopoverWrapper listViewPopover = listView.AsPopover ();
+
+listView.SelectedItemChanged += (sender, args) =>
+{
+ listViewPopover.Visible = false;
+
+ if (listView.SelectedItem is { } selectedItem && selectedItem >= 0)
+ {
+ MessageBox.Query (app, "Selected", $"You selected: {listView.Source.ToList () [selectedItem]}", "OK");
+ }
+};
+
+button2.Accepting += (s, e) =>
+{
+ IApplication? application = (s as View)?.App;
+
+ if (application is null)
+ {
+ return;
+ }
+
+ listViewPopover.X = Pos.Center ();
+ listViewPopover.Y = Pos.Center ();
+ application.Popover?.Register (listViewPopover);
+ application.Popover?.Show (listViewPopover);
+
+ e.Handled = true;
+};
+
+mainWindow.Add (button2);
+
+// Example 3: Form as popover
+Button button3 = new ()
+{
+ Title = "_3: Form Popover",
+ X = Pos.Center (),
+ Y = Pos.Bottom (button2) + 1
+};
+
+button3.Accepting += (s, e) =>
+{
+ IApplication? application = (s as View)?.App;
+
+ if (application is null)
+ {
+ return;
+ }
+
+ View formView = CreateFormView (application);
+ PopoverWrapper popover = formView.AsPopover ();
+ popover.X = Pos.Center ();
+ popover.Y = Pos.Center ();
+ application.Popover?.Register (popover);
+ application.Popover?.Show (popover);
+ e.Handled = true;
+};
+
+mainWindow.Add (button3);
+
+// Example 4: ColorPicker as popover
+Button button4 = new ()
+{
+ Title = "_4: ColorPicker Popover",
+ X = Pos.Center (),
+ Y = Pos.Bottom (button3) + 1
+};
+
+button4.Accepting += (s, e) =>
+{
+ IApplication? application = (s as View)?.App;
+
+ if (application is null)
+ {
+ return;
+ }
+
+ ColorPicker colorPicker = new ()
+ {
+ Title = "Pick a Border Color",
+ BorderStyle = LineStyle.Single
+ };
+
+ colorPicker.Selecting += (sender, args) =>
+ {
+ ColorPicker? picker = sender as ColorPicker;
+
+ if (picker is { })
+ {
+ Scheme old = application.TopRunnableView.Border.GetScheme ();
+ application.TopRunnableView.Border.SetScheme (old with { Normal = new Attribute (picker.SelectedColor, Color.Black) });
+ }
+ args.Handled = true;
+ };
+
+ PopoverWrapper popover = colorPicker.AsPopover ();
+ popover.X = Pos.Center ();
+ popover.Y = Pos.Center ();
+ application.Popover?.Register (popover);
+ application.Popover?.Show (popover);
+
+ e.Handled = true;
+};
+
+mainWindow.Add (button4);
+
+// Example 5: Custom position and size
+Button button5 = new ()
+{
+ Title = "_5: Positioned Popover",
+ X = Pos.Center (),
+ Y = Pos.Bottom (button4) + 1
+};
+
+button5.Accepting += (s, e) =>
+{
+ IApplication? application = (s as View)?.App;
+
+ if (application is null)
+ {
+ return;
+ }
+
+ View customView = new ()
+ {
+ Title = "Custom Position",
+ X = Pos.Percent (10),
+ Y = Pos.Percent (10),
+ Width = Dim.Percent (50),
+ Height = Dim.Percent (60),
+ BorderStyle = LineStyle.Double
+ };
+
+ customView.Add (
+ new Label
+ {
+ Text = "This popover has a custom position and size.\n\nYou can set X, Y, Width, and Height\nusing Pos and Dim to position it anywhere.",
+ X = 2,
+ Y = 2
+ });
+
+ Button closeButton = new ()
+ {
+ Title = "Close",
+ X = Pos.Center (),
+ Y = Pos.AnchorEnd (1)
+ };
+
+ closeButton.Accepting += (sender, args) =>
+ {
+ if (customView.SuperView is PopoverWrapper wrapper)
+ {
+ wrapper.Visible = false;
+ }
+
+ args.Handled = true;
+ };
+
+ customView.Add (closeButton);
+
+ PopoverWrapper popover = customView.AsPopover ();
+ application.Popover?.Register (popover);
+ popover.X = Pos.Center ();
+ popover.Y = Pos.Center ();
+ application.Popover?.Show (popover);
+
+ e.Handled = true;
+};
+
+mainWindow.Add (button5);
+
+// Quit button
+Button quitButton = new ()
+{
+ Title = "_Quit",
+ X = Pos.Center (),
+ Y = Pos.AnchorEnd (1)
+};
+
+quitButton.Accepting += (s, e) =>
+{
+ app.RequestStop ();
+ e.Handled = true;
+};
+
+mainWindow.Add (quitButton);
+
+app.Run (mainWindow);
+mainWindow.Dispose ();
+app.Dispose ();
+
+// Helper method to create a form view
+View CreateFormView (IApplication application)
+{
+ View form = new ()
+ {
+ Title = "User Registration Form",
+ X = Pos.Center (),
+ Y = Pos.Center (),
+ Width = Dim.Percent (60),
+ Height = Dim.Percent (50),
+ BorderStyle = LineStyle.Single
+ };
+
+ Label nameLabel = new ()
+ {
+ Text = "Name:",
+ X = 2,
+ Y = 1
+ };
+
+ TextField nameField = new ()
+ {
+ X = Pos.Right (nameLabel) + 2,
+ Y = Pos.Top (nameLabel),
+ Width = Dim.Fill (2)
+ };
+
+ Label emailLabel = new ()
+ {
+ Text = "Email:",
+ X = Pos.Left (nameLabel),
+ Y = Pos.Bottom (nameLabel) + 1
+ };
+
+ TextField emailField = new ()
+ {
+ X = Pos.Right (emailLabel) + 2,
+ Y = Pos.Top (emailLabel),
+ Width = Dim.Fill (2)
+ };
+
+ Label ageLabel = new ()
+ {
+ Text = "Age:",
+ X = Pos.Left (nameLabel),
+ Y = Pos.Bottom (emailLabel) + 1
+ };
+
+ TextField ageField = new ()
+ {
+ X = Pos.Right (ageLabel) + 2,
+ Y = Pos.Top (ageLabel),
+ Width = Dim.Percent (20)
+ };
+
+ CheckBox agreeCheckbox = new ()
+ {
+ Title = "I agree to the terms and conditions",
+ X = Pos.Left (nameLabel),
+ Y = Pos.Bottom (ageLabel) + 1
+ };
+
+ Button submitButton = new ()
+ {
+ Title = "Submit",
+ X = Pos.Center () - 8,
+ Y = Pos.AnchorEnd (2),
+ IsDefault = true
+ };
+
+ submitButton.Accepting += (s, e) =>
+ {
+ if (string.IsNullOrWhiteSpace (nameField.Text))
+ {
+ MessageBox.ErrorQuery (application, "Error", "Name is required!", "OK");
+ e.Handled = true;
+
+ return;
+ }
+
+ if (agreeCheckbox.CheckedState != CheckState.Checked)
+ {
+ MessageBox.ErrorQuery (application, "Error", "You must agree to the terms!", "OK");
+ e.Handled = true;
+
+ return;
+ }
+
+ MessageBox.Query (
+ application,
+ "Success",
+ $"Registration submitted!\n\nName: {nameField.Text}\nEmail: {emailField.Text}\nAge: {ageField.Text}",
+ "OK");
+
+ if (form.SuperView is PopoverWrapper wrapper)
+ {
+ wrapper.Visible = false;
+ }
+
+ e.Handled = true;
+ };
+
+ Button cancelButton = new ()
+ {
+ Title = "Cancel",
+ X = Pos.Center () + 4,
+ Y = Pos.Top (submitButton)
+ };
+
+ cancelButton.Accepting += (s, e) =>
+ {
+ if (form.SuperView is PopoverWrapper wrapper)
+ {
+ wrapper.Visible = false;
+ }
+
+ e.Handled = true;
+ };
+
+ form.Add (nameLabel, nameField);
+ form.Add (emailLabel, emailField);
+ form.Add (ageLabel, ageField);
+ form.Add (agreeCheckbox);
+ form.Add (submitButton, cancelButton);
+
+ return form;
+}
diff --git a/Examples/PopoverWrapperExample/README.md b/Examples/PopoverWrapperExample/README.md
new file mode 100644
index 000000000..2600ebe21
--- /dev/null
+++ b/Examples/PopoverWrapperExample/README.md
@@ -0,0 +1,108 @@
+# PopoverWrapper Example
+
+This example demonstrates how to use `PopoverWrapper` to make any View into a popover without implementing the `IPopover` interface.
+
+## Overview
+
+`PopoverWrapper` is similar to `RunnableWrapper` but for popovers instead of runnables. It wraps any View and automatically handles:
+
+- Setting proper viewport settings (transparent, transparent mouse)
+- Configuring focus behavior
+- Handling the quit command to hide the popover
+- Sizing to fill the screen by default
+
+## Key Features
+
+- **Fluent API**: Use `.AsPopover()` extension method for a clean, fluent syntax
+- **Any View**: Wrap any existing View - Button, ListView, custom Views, forms, etc.
+- **Automatic Management**: The wrapper handles all the popover boilerplate
+- **Type-Safe**: Generic type parameter ensures type safety when accessing the wrapped view
+
+## Usage
+
+### Basic Usage
+
+```csharp
+// Create any view
+var myView = new View
+{
+ X = Pos.Center (),
+ Y = Pos.Center (),
+ Width = 40,
+ Height = 10,
+ BorderStyle = LineStyle.Single
+};
+
+// Wrap it as a popover
+PopoverWrapper popover = myView.AsPopover ();
+
+// Register and show
+app.Popover.Register (popover);
+app.Popover.Show (popover);
+```
+
+### With ListView
+
+```csharp
+var listView = new ListView
+{
+ Title = "Select an Item",
+ X = Pos.Center (),
+ Y = Pos.Center (),
+ Width = 30,
+ Height = 10,
+ Source = new ListWrapper (["Apple", "Banana", "Cherry"])
+};
+
+PopoverWrapper popover = listView.AsPopover ();
+app.Popover.Register (popover);
+app.Popover.Show (popover);
+```
+
+### With Custom Forms
+
+```csharp
+View CreateFormView ()
+{
+ var form = new View
+ {
+ Title = "User Form",
+ X = Pos.Center (),
+ Y = Pos.Center (),
+ Width = 60,
+ Height = 16
+ };
+
+ // Add form fields...
+
+ return form;
+}
+
+View formView = CreateFormView ();
+PopoverWrapper popover = formView.AsPopover ();
+app.Popover.Register (popover);
+app.Popover.Show (popover);
+```
+
+## Comparison with RunnableWrapper
+
+| Feature | RunnableWrapper | PopoverWrapper |
+|---------|----------------|----------------|
+| Purpose | Make any View runnable as a modal session | Make any View into a popover |
+| Blocking | Yes, blocks until stopped | No, non-blocking overlay |
+| Result Extraction | Yes, via typed Result property | N/A (access WrappedView directly) |
+| Dismissal | Via RequestStop() or Quit command | Via Quit command or clicking outside |
+| Focus | Takes exclusive focus | Shares focus with underlying content |
+
+## Running the Example
+
+```bash
+dotnet run --project Examples/PopoverWrapperExample
+```
+
+## See Also
+
+- [Popovers Deep Dive](../../docfx/docs/Popovers.md)
+- [RunnableWrapper Example](../RunnableWrapperExample/)
+- `Terminal.Gui.App.PopoverBaseImpl`
+- `Terminal.Gui.App.IPopover`
diff --git a/Examples/UICatalog/Scenarios/DropDownListExample.cs b/Examples/UICatalog/Scenarios/DropDownListExample.cs
index 51f1cc4c1..29615a3bf 100644
--- a/Examples/UICatalog/Scenarios/DropDownListExample.cs
+++ b/Examples/UICatalog/Scenarios/DropDownListExample.cs
@@ -18,34 +18,11 @@ public sealed class DropDownListExample : Scenario
BorderStyle = LineStyle.None
};
- Label l = new Label () { Title = "_DropDown:" };
+ Label label = new Label () { Title = "_DropDown TextField Using Menu:" };
+ View view = CreateDropDownTextFieldUsingMenu ();
+ view.X = Pos.Right (label) + 1;
- TextField tf = new () { X = Pos.Right(l), Width = 10 };
-
- MenuBarItem? menuBarItem = new ($"{Glyphs.DownArrow}",
- Enumerable.Range (1, 5)
- .Select (selector: i => new MenuItem($"item {i}", null, null, null) )
- .ToArray ());
-
- var mb = new MenuBar ([menuBarItem])
- {
- CanFocus = true,
- Width = 1,
- Y = Pos.Top (tf),
- X = Pos.Right (tf)
- };
-
- // HACKS required to make this work:
- mb.Accepted += (s, e) => {
- // BUG: This does not select menu item 0
- // Instead what happens is the first keystroke the user presses
- // gets swallowed and focus is moved to 0. Result is that you have
- // to press down arrow twice to select first menu item and/or have to
- // press Tab twice to move focus back to TextField
- mb.OpenMenu ();
- };
-
- appWindow.Add (l, tf, mb);
+ appWindow.Add (label, view);
// Run - Start the application.
Application.Run (appWindow);
@@ -54,4 +31,140 @@ public sealed class DropDownListExample : Scenario
// Shutdown - Calling Application.Shutdown is required.
Application.Shutdown ();
}
+
+ private View CreateDropDownTextFieldUsingMenu ()
+ {
+
+ TextField tf = new ()
+ {
+ Text = "item 1",
+ Width = 10,
+ Height = 1
+ };
+
+ MenuBarItem? menuBarItem = new ($"{Glyphs.DownArrow}", Enumerable.Range (1, 5)
+ .Select (i =>
+ {
+ MenuItem item = new MenuItem ($"item {i}", null, null, null);
+ item.Accepting += (s, e) =>
+ {
+ tf.Text = item.Title;
+ //e.Handled = true;
+ };
+
+ return item;
+ })
+ .ToArray ())
+ {
+ MarginThickness = Thickness.Empty
+ };
+
+ menuBarItem.PopoverMenuOpenChanged += (s, e) =>
+ {
+ if (e.Value && s is MenuBarItem sender)
+ {
+ sender.PopoverMenu!.Root.X = tf.FrameToScreen ().X;
+ sender.PopoverMenu.Root.Width = tf.Width + sender.Width;
+ // Find the subview of Root whos Text matches tf.Text and setfocus to it
+ var menuItemToSelect = sender.PopoverMenu.Root.SubViews.OfType