Explorar o código

Fixes #4372 - Genericize `FlagSelector`/`OptionSelector`, Replace `RadioGroup` (#4373)

* Refactor selectors and improve UI components

Refactored `MarginEditor` and `UICatalogTop` to use new `OptionSelector` and `FlagSelector` classes, introducing type-safe generic versions for better flexibility and maintainability. Added `SelectorBase` as a shared foundation for these components, along with the `SelectorStyles` enum for customizable styles.

Enhanced unit tests to cover new implementations and edge cases. Enabled nullable reference types for improved null safety. Improved code readability, reduced redundancy, and enhanced user experience with better hotkey management, focus handling, and layout adjustments.

* Refactor UI components and remove unused classes

Significant refactoring and simplification of the codebase:
- Updated `CharacterMap` to use `OptionSelector<UnicodeCategory>`.
- Removed `FlagSelector`, `FlagSelector<TEnum>`, and `FlagSelectorStyles`.
- Replaced `OptionSelector.Options` with `Labels` in `MenuBarv2`.
- Removed `OptionSelector` and its associated properties/methods.
- Updated terminology from "Activate" to "Select" across components.
- Refactored `SelectorBase` to align with new "Select" behavior.
- Removed redundant methods, properties, and event handlers.

These changes streamline the codebase, reduce complexity, and align with updated design principles.

* Fixes #4374 - 'Application.Screen' is empty when 'Init' returns

Refactor and enhance testability of ApplicationImpl

Refactored `ApplicationImpl` and related classes to improve modularity and testability. Replaced `FakeConsoleOutput` with `FakeOutput` and introduced `FakeInput` for better test isolation. Added platform-specific factories (`FakeNetComponentFactory`, `FakeWindowsComponentFactory`) to simplify fake component creation.

Refactored `GuiTestContext` into partial classes, adding methods for simulating user interactions and improving initialization logic. Enhanced error handling and logging during test setup.

Updated tests to use the new `FakeOutput` and `FakeInput` implementations. Standardized driver initialization with `Application.Init(null, "fake")`. Skipped tests relying on the fake driver due to known issues.

Performed general cleanup, modernized syntax, and removed redundant code to improve readability and maintainability.

* Disable "windows" test case in SynchronizationContextTests

The `InlineData("windows")` attribute in the
`SynchronizationContext_Post` test method has been commented out.
This change temporarily excludes the `"windows"` driverName from
the test suite while retaining other test cases (`"fake"`,
`"dotnet"`, and `"unix"`). The exclusion may be for debugging,
deprecation, or other maintenance purposes.

* Disable "windows" test case in SynchronizationContextTests

The `[InlineData("windows")]` attribute in the
`SynchronizationContextTests` class has been commented out,
disabling the test case for the `"windows"` driver name.

This change may have been made for debugging, deprecation, or
because the test is no longer relevant. Other test cases
(`"fake"`, `"dotnet"`, and `"unix"`) remain active.

* Update Terminal.Gui/Drivers/FakeDriver/FakeConsole.cs

Co-authored-by: Copilot <[email protected]>

* Update Terminal.Gui/Views/Selectors/SelectorStyles.cs

Co-authored-by: Copilot <[email protected]>

* Update Terminal.Gui/Views/Selectors/SelectorBase.cs

Co-authored-by: Copilot <[email protected]>

* Update Terminal.Gui/Views/Selectors/OptionSelector.cs

Co-authored-by: Copilot <[email protected]>

* Update Terminal.Gui/Views/Selectors/OptionSelector.cs

Co-authored-by: Copilot <[email protected]>

* Update Terminal.Gui/Views/Selectors/OptionSelector.cs

Co-authored-by: Copilot <[email protected]>

* Update Terminal.Gui/Views/Selectors/SelectorBase.cs

Co-authored-by: Copilot <[email protected]>

* Backported Checkbox from Activate PR

* Backported Checkbox from Activate PR 2

* Backported Checkbox from Activate PR 3

* Backported Selctors Scenario

* Backported Bars Scenario

* Backported AllViewsTester Scenario

* Backported Dialogs Scenario

* Backported MessageBoxes Scenario

* Backported ArrangementEditor

* Backported mouse binding fix

* Update Terminal.Gui/Views/Selectors/OptionSelector.cs

Co-authored-by: Copilot <[email protected]>

* Update Terminal.Gui/Drivers/WindowsDriver/WindowsOutput.cs

Co-authored-by: Copilot <[email protected]>

* Update Terminal.Gui/Views/CheckBox.cs

Co-authored-by: Copilot <[email protected]>

* Fixed typo

* Refactor ArrangementEditor event handling

Removed the `ArrangementFlagsOnValueChanged` method, which previously handled updates to `ViewToEdit` properties based on arrangement flags. Updated `ArrangementEditor_Initialized` to attach the event handler to `_arrangementSelector.ValueChanged`. The logic for handling arrangement changes has been refactored or relocated.

* Refactor AlignKeys for type safety and readability

Updated the `AlignKeys` method in the `Shortcuts` class to replace generic `View` references with the more specific `Shortcut` type. Improved type safety by using `IEnumerable<Shortcut>` and `.Cast<Shortcut>()`. Simplified the `max` calculation logic with a single LINQ query and removed redundant casting in the `foreach` loop. These changes enhance code readability, maintainability, and ensure better type safety.

* Refactor ArrangementEditor for clarity and consistency

Refactored `ArrangementEditor` to improve code readability and maintainability:
- Enabled nullable reference types with `#nullable enable`.
- Removed unused `using` directives.
- Adjusted namespace declaration for formatting consistency.
- Reformatted `_arrangementSelector` initialization and property assignment.
- Simplified `OnViewToEditChanged` logic with a ternary expression.
- Refactored `ArrangementEditor_Initialized` into a single-line block.

* Update Examples/UICatalog/Scenarios/Shortcuts.cs

Co-authored-by: Copilot <[email protected]>

* Update Terminal.Gui/Views/Selectors/OptionSelector.cs

Co-authored-by: Copilot <[email protected]>

* Refactor and enhance OptionSelector and SelectorBase

Refactored `OptionSelector` and `SelectorBase` to simplify logic, improve hotkey assignment, and ensure robust behavior. Updated `Shortcuts.cs` and `DialogTests.cs` to address nullability issues.

Added comprehensive unit tests for `OptionSelector` and `SelectorBase`, covering properties, methods, edge cases, and layout behaviors. These changes improve code readability, maintainability, and functionality while adhering to modern C# practices.

* add FlagSelector comprehensive tests

Refactored `UncheckNone` and `UncheckAll` methods in `FlagSelector` to improve clarity and prevent concurrent modifications using a new `_updatingChecked` flag. Removed the old `UncheckNone` implementation and reorganized logic for maintainability.

Added extensive unit tests in `FlagSelectorTests` to validate functionality, including edge cases and generic implementations. Tests cover flag combination, toggling, "None" flag behavior, and enum-based generic handling.

Improved overall maintainability and test coverage for the `FlagSelector` class.

* Fixes #4375. UnixDriver fails Toplevel_TabGroup_Forward_Backward Fluent Tests

* Refactor RadioGroup to use OptionSelector

The `RadioGroup` class has been refactored to inherit from the `OptionSelector` class instead of `View`, marking it as `[Obsolete]` and recommending the use of `OptionSelector`.

The previous implementation of `RadioGroup` has been entirely removed, including its properties, methods, events, and internal logic. This includes initialization logic, key bindings, layout management, and event handling.

The new `RadioGroup` is now a thin wrapper around `OptionSelector` and implements the `IDesignable` interface. The `EnableForDesign` method has been simplified to set default options for design purposes.

This change simplifies the codebase and encourages the use of `OptionSelector` for managing mutually exclusive options.

* Backported focus tests and add bug-exposing test case

Refactored `AdvanceFocusTests` to improve assertion clarity by replacing `Assert.True`/`Assert.False` with `Assert.Equal`. Enhanced test documentation with detailed view hierarchy comments for better readability.

Added a new test case, `FocusNavigation_Should_Cycle_Back_To_Top_Level_Views`, which exposes a bug in focus navigation logic where focus does not cycle back to top-level views after traversing nested views.

Updated existing tests to ensure consistent handling of `TabBehavior` and made minor adjustments for improved validation of focus navigation logic.

* Remove all tests for RadioGroup component

The `RadioGroupTests.cs` file has been completely cleared of all test cases and associated code. This includes the removal of unit tests that validated the `RadioGroup` component's functionality, behavior, and edge cases.

The deleted tests covered:
- Default constructor behavior and initialization.
- Handling of the `SelectedItem` property, including edge cases.
- Hotkey bindings and their behavior under different focus states.
- Command handling for focus, selection, and acceptance.
- Orientation changes and their impact on layout.
- Event handling for `SelectedItemChanged`, `Selecting`, and `Accepting`.
- Mouse interactions, including single-click and double-click events.

This removal eliminates all automated validation for the `RadioGroup` component, leaving it untested and increasing the risk of regressions or undetected issues in future changes.

* Fix unix and fake fluent tests.

* More fixes for unix and fake drivers

* Change classes names for more consistency

* Fix typos in docs and method signature

Updated XML documentation in `FakeConsole.cs` to replace `<see cref="FakeDriver"/>` with `<exception cref="FakeDriver"></exception>` for clarity.

Corrected a parameter name in `WindowsOutput.cs`'s `WriteConsole` method from `numberOfCharsToWritten` to `numberOfCharsToWrite` to fix a typo and improve readability.

* Refactor: Replace RadioGroup with OptionSelector

Replaced all instances of `RadioGroup` with `OptionSelector` across the codebase to standardize the control for mutually exclusive options. Updated associated properties, methods, and event handlers to align with the `OptionSelector` API, including replacing `RadioLabels` with `AssignHotKeys` and `SelectedItemChanged` with `ValueChanged`.

Removed the `RadioGroup` class, marking it as obsolete. Updated documentation, comments, and test cases to reflect the new control. Adjusted layout and positioning logic in various scenarios to ensure UI consistency.

Refactored scenarios such as `Buttons`, `ColorPickers`, `DynamicMenuBar`, `FileDialogExamples`, `Images`, `PosAlignDemo`, `ProgressBarStyles`, `RegionScenario`, `Themes`, and others to use `OptionSelector`. Updated `Glyphs` and `View` classes to reflect the terminology change. Cleaned up redundant code and ensured compatibility across the application.

* Refactor OptionSelector to use Value instead of SelectedItem

Replaced the SelectedItem property with a nullable Value property across the codebase to simplify the API and improve consistency. Updated event handlers from SelectedItemChanged to ValueChanged and adjusted logic accordingly.

Refactored UI scenarios (e.g., Buttons, CharacterMap, ColorPickers) and dependent classes (e.g., BorderEditor, DimEditor, PosEditor) to use the new Value property. Improved null handling and streamlined initialization of controls.

Updated tests to validate the Value property and renamed test methods for clarity. Removed the RegionOpSelector class as it was no longer needed. Performed general code cleanup, including formatting and removal of redundant code.

* Refactor OptionSelector: Replace RadioLabels with Labels

Updated the `OptionSelector` class and its derived classes to replace the `RadioLabels` property with a more generic `Labels` property, aligning with the base class `SelectorBase`. This change standardizes the API and simplifies label-related functionality.

Refactored all instances of `RadioLabels` across the codebase, including property assignments, method calls, and references in scenarios, tests, and examples. Updated classes include `ColorPickers`, `Dialogs`, `DimAutoDemo`, `DynamicMenuBar`, `FileDialogExamples`, `Images`, `PosAlignDemo`, `Selectors`, `Shortcuts`, `TextAlignmentAndDirection`, `Themes`, `UnicodeInMenu`, `Wizards`, `UICatalogTop`, and `ScenarioTests`.

Modified `OptionSelector<TEnum>` to initialize `Labels` directly using `Enum.GetValues<TEnum>()`. Removed the `RadioLabels` property from `OptionSelector`, consolidating functionality under `Labels`.

Verified functionality through updated tests and scenarios to ensure consistent behavior with the previous implementation.

* Refactor: Replace "radio group" with "option selector"

Updated terminology across multiple classes to replace "radio group" with "option selector" for improved clarity and consistency.

- Removed unused `OptionSelector` in `ColorPickers`.
- Renamed `Title` in `DimAutoDemo` to "Options" and updated `BorderStyle`.
- Replaced `_radioItems` with `_optionLabels` in `DimEditor` and `PosEditor`.
- Renamed `styleRadioGroup` to `styleOptionSelector` in `MessageBoxes`.
- Renamed `radioGroup` to `optionSelector` in `UnicodeInMenu` and `OrientationTests`.
- Adjusted related references, event handlers, and UI properties.

These changes align the codebase with updated terminology and improve readability.

* Replace RadioGroup with OptionSelector and update docs

The `RadioGroup` control has been replaced or renamed to `OptionSelector`. Documentation has been updated to reflect this change, including the behavior of raising the `Selecting` event when an option is selected.

The navigation table now describes `OptionSelector` as supporting multiple options with actions like `Advance`, `SetValue+OnAccept`, and `Focus+SetValue`. A new section introduces the `OptionSelector` view, which displays mutually-exclusive items with hotkeys.

Enhancements to `Menuv2` and `MenuBarv2` include setting focus on `MenuItemv2` selections and raising the `SelectedMenuItemChanged` event. Additionally, a progress bar view has been introduced to visually indicate activity progress.

* Fixed `EndAfterFirstIteration`

Renamed the `EndAfterFirstIteration` property to `StopAfterFirstIteration` across the codebase for improved clarity and consistency. Updated all references in the `Application`, `ApplicationImpl`, `IApplication`, and `ITimedEvents` classes, as well as related tests and documentation.

Modified the application loop logic to use `StopAfterFirstIteration` for controlling the termination of the application after the first iteration. Set its default value to `false`.

Updated test cases, demo applications, and XML documentation to reflect the new property name. Added a new project, `OutputView`, to the solution with appropriate configuration entries.

Performed minor code cleanup to ensure consistency in naming and behavior.

* Enhance selectors and clean up documentation

- Added `args.Handled = true` to `CheckBox` event handlers in `FlagSelector` and `OptionSelector` to mark events as handled.
- Introduced `_value` field in `FlagSelector` and added a `Cycle` method in `OptionSelector` for better value management.
- Updated `OptionSelector` documentation to reference `OptionSelector<TEnum>` for type-safe enum usage.
- Improved `UpdateChecked` method documentation in `OptionSelector` to clarify exception behavior.
- Enabled nullable reference types in `FlagSelectorTests` and `SelectorBaseTests` and moved them to a new namespace.
- Removed outdated auto-generated content from `views.md`.
- Removed `CheckBox.DefaultHighlightStyle` from the default theme configuration in `OutputView.cs`.

* Update event handling and expand UI documentation

Modified `args.Handled` in `FlagSelector` and `OptionSelector` to allow `Accepting` event propagation, improving event handling behavior. Added comments to clarify the changes.

Expanded `views.md` with detailed documentation for built-in views and controls in *Terminal.Gui*, including descriptions, examples, and rendered outputs for components like `Bar`, `Button`, `CheckBox`, and more. This update enhances developer guidance for building terminal-based UIs.

* Fixed `EndAfterFirstIteration` in `ApplicationImpl`

Renamed the `EndAfterFirstIteration` property to `StopAfterFirstIteration` across the codebase for improved clarity. Updated its implementation to use a getter and setter that interact with the `ApplicationImpl.Instance` singleton for centralized management.

Modified the `RunLoop` method to check the new `StopAfterFirstIteration` property. Updated the default value to `false` in the `Application` class.

Added a private `_stopAfterFirstIteration` field and a corresponding public property in the `ApplicationImpl` class. Updated the `Run` method in `ApplicationImpl` to stop after the first iteration if the property is set to `true`, with appropriate logging.

Updated the `IApplication` interface to include the `StopAfterFirstIteration` property and clarified the behavior of the `RequestStop` method. Revised XML documentation comments to reflect these changes.

* Fixed EndfterFirstIteration in ApplicaitonImpl

Refactored `StopAfterFirstIteration` in `ApplicationImpl` to use an auto-property for simplicity. Updated `RunIteration` to call `view.RequestStop()` instead of modifying `view.Running`.

Replaced references to `Application.EndAfterFirstIteration` with `Application.StopAfterFirstIteration` across the codebase, including `ITimedEvents`, `ApplicationTests`, and `GlobalTestSetup`.

Added a new test, `InitRunShutdown_StopAfterFirstIteration_Stops`, to verify the application stops correctly after the first iteration. Updated related documentation and assertions for consistency.

* Refactor Value handling and improve type safety

Refactored `Value` handling across multiple classes to use nullable generic types, improving type safety and eliminating unnecessary casting. Simplified `ValueChanged` event handlers with concise lambda expressions.

Enhanced `FlagSelector<TFlagsEnum>` and `OptionSelector<TEnum>` with generic `ValueChanged` events and type-safe event handling. Added nullable reference type annotations to align with modern C# practices.

Improved test code by using null-forgiving operators and more descriptive assertions. Cleaned up redundant code and ensured consistency in `Value` handling. Updated `FlagSelectorTests` and `SelectorBaseTests` for better readability and maintainability.

Added the `System` namespace to `FlagSelectorTEnum.cs` for compatibility. Overall, these changes enhance code readability, maintainability, and robustness.

* Merged v2_develop

* Update README badges for v2_develop branch

Updated the `.NET Core` badge to reference the `v2_develop` branch. Adjusted the `codecov` badge to remove branch-specific paths and added a token parameter. Reorganized the `codecov` badge position in the README. Retained other badges without modification.

* codcov2

* fixed pos tests

* Improve cleanup, coverage config, and SpinnerStyle tests

Enhanced resource cleanup in `Pos.CombineTests.cs` by disposing of `Application.Top` to prevent leaks. Updated `codecov.yml` to focus coverage on `Terminal.Gui`, simplified path patterns, and clarified configurations.

Added `SpinnerStyleTests` with extensive unit tests for `SpinnerStyle` and its variants, covering default properties, behaviors, edge cases, and immutability. Organized tests for readability and ensured thorough validation of all spinner styles. Enabled nullable reference types for improved safety.

* Remove .NET Core badge; add comprehensive boundary tests

The `.NET Core` workflow badge was removed from the `README.md` file.

Added a comprehensive suite of unit tests for the `Region.DrawOuterBoundary` method in `DrawOuterBoundaryTests.cs`. These tests validate the method's behavior across various scenarios, including:
- Intersected, unioned, and complex shapes.
- Edge cases like empty regions, zero-width/height rectangles, and single-pixel rectangles.
- Specific shapes such as L-shaped, T-shaped, and hollow rectangles.
- Overlapping, adjacent, and separate rectangles.
- Thread safety with parallel drawing.
- Different line styles, custom attributes, and very large regions.
- Various positions, sizes, and multiple calls on the same canvas.

The tests use the `Xunit` framework and include both `[Fact]` and `[Theory]` test cases. These changes enhance the codebase's robustness and ensure correctness in a wide range of scenarios.

---------

Co-authored-by: Copilot <[email protected]>
Co-authored-by: BDisp <[email protected]>
Tig hai 1 mes
pai
achega
be9d1939c1
Modificáronse 65 ficheiros con 4510 adicións e 3329 borrados
  1. 5 9
      Examples/UICatalog/Scenarios/AllViewsTester.cs
  2. 2 2
      Examples/UICatalog/Scenarios/Bars.cs
  3. 22 49
      Examples/UICatalog/Scenarios/Buttons.cs
  4. 6 30
      Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs
  5. 19 15
      Examples/UICatalog/Scenarios/ColorPicker.cs
  6. 7 10
      Examples/UICatalog/Scenarios/Dialogs.cs
  7. 4 4
      Examples/UICatalog/Scenarios/DimAutoDemo.cs
  8. 10 10
      Examples/UICatalog/Scenarios/DynamicMenuBar.cs
  9. 13 79
      Examples/UICatalog/Scenarios/EditorsAndHelpers/ArrangementEditor.cs
  10. 14 13
      Examples/UICatalog/Scenarios/EditorsAndHelpers/BorderEditor.cs
  11. 9 9
      Examples/UICatalog/Scenarios/EditorsAndHelpers/DimEditor.cs
  12. 14 22
      Examples/UICatalog/Scenarios/EditorsAndHelpers/MarginEditor.cs
  13. 9 9
      Examples/UICatalog/Scenarios/EditorsAndHelpers/PosEditor.cs
  14. 9 10
      Examples/UICatalog/Scenarios/EditorsAndHelpers/ViewPropertiesEditor.cs
  15. 133 133
      Examples/UICatalog/Scenarios/FileDialogExamples.cs
  16. 16 16
      Examples/UICatalog/Scenarios/Images.cs
  17. 10 4
      Examples/UICatalog/Scenarios/LineDrawing.cs
  18. 9 8
      Examples/UICatalog/Scenarios/MessageBoxes.cs
  19. 141 135
      Examples/UICatalog/Scenarios/PosAlignDemo.cs
  20. 17 11
      Examples/UICatalog/Scenarios/ProgressBarStyles.cs
  21. 18 45
      Examples/UICatalog/Scenarios/RegionScenario.cs
  22. 5 9
      Examples/UICatalog/Scenarios/ScrollBarDemo.cs
  23. 287 0
      Examples/UICatalog/Scenarios/Selectors.cs
  24. 102 65
      Examples/UICatalog/Scenarios/Shortcuts.cs
  25. 10 10
      Examples/UICatalog/Scenarios/TextAlignmentAndDirection.cs
  26. 6 6
      Examples/UICatalog/Scenarios/Themes.cs
  27. 5 5
      Examples/UICatalog/Scenarios/Unicode.cs
  28. 6 6
      Examples/UICatalog/Scenarios/Wizards.cs
  29. 52 44
      Examples/UICatalog/UICatalogTop.cs
  30. 0 1
      README.md
  31. 1 0
      Terminal.Gui/App/ApplicationImpl.cs
  32. 2 2
      Terminal.Gui/Drawing/Glyphs.cs
  33. 1 0
      Terminal.Gui/Drivers/WindowsDriver/WindowsOutput.cs
  34. 11 1
      Terminal.Gui/Input/Mouse/MouseBinding.cs
  35. 1 1
      Terminal.Gui/ViewBase/View.Keyboard.cs
  36. 40 26
      Terminal.Gui/Views/CheckBox.cs
  37. 0 513
      Terminal.Gui/Views/FlagSelector.cs
  38. 0 31
      Terminal.Gui/Views/FlagSelectorStyles.cs
  39. 0 99
      Terminal.Gui/Views/FlagSelectorTEnum.cs
  40. 2 2
      Terminal.Gui/Views/Menu/MenuBarv2.cs
  41. 0 324
      Terminal.Gui/Views/OptionSelector.cs
  42. 0 604
      Terminal.Gui/Views/RadioGroup.cs
  43. 244 0
      Terminal.Gui/Views/Selectors/FlagSelector.cs
  44. 46 0
      Terminal.Gui/Views/Selectors/FlagSelectorTEnum.cs
  45. 249 0
      Terminal.Gui/Views/Selectors/OptionSelector.cs
  46. 53 0
      Terminal.Gui/Views/Selectors/OptionSelectorTEnum.cs
  47. 509 0
      Terminal.Gui/Views/Selectors/SelectorBase.cs
  48. 42 0
      Terminal.Gui/Views/Selectors/SelectorStyles.cs
  49. 4 0
      Terminal.sln
  50. 22 22
      Tests/IntegrationTests/UICatalog/ScenarioTests.cs
  51. 2 1
      Tests/TerminalGuiFluentTesting/GuiTestContext.cs
  52. 2 6
      Tests/UnitTests/View/Layout/Pos.CombineTests.cs
  53. 4 4
      Tests/UnitTests/Views/CheckBoxTests.cs
  54. 0 775
      Tests/UnitTests/Views/RadioGroupTests.cs
  55. 531 0
      Tests/UnitTestsParallelizable/Drawing/Region/DrawOuterBoundaryTests.cs
  56. 87 13
      Tests/UnitTestsParallelizable/View/Navigation/AdvanceFocusTests.cs
  57. 4 4
      Tests/UnitTestsParallelizable/View/Orientation/OrientationTests.cs
  58. 1 1
      Tests/UnitTestsParallelizable/Views/CheckBoxTests.cs
  59. 601 99
      Tests/UnitTestsParallelizable/Views/FlagSelectorTests.cs
  60. 447 0
      Tests/UnitTestsParallelizable/Views/OptionSelectorTests.cs
  61. 604 0
      Tests/UnitTestsParallelizable/Views/SelectorBaseTests.cs
  62. 1 1
      docfx/docs/command.md
  63. 1 1
      docfx/docs/navigation.md
  64. 37 28
      docfx/docs/views.md
  65. 1 2
      docfx/scripts/OutputView/OutputView.cs

+ 5 - 9
Examples/UICatalog/Scenarios/AllViewsTester.cs

@@ -158,16 +158,13 @@ public class AllViewsTester : Scenario
 
         _eventLog = new ()
         {
-            // X = Pos.Right(_layoutEditor),
+            X = Pos.AnchorEnd () - 1,
+            Y = 0,
+            Width = 30,
+            Height = Dim.Fill (),
             SuperViewRendersLineCanvas = true
         };
         _eventLog.Border!.Thickness = new (1);
-        _eventLog.X = Pos.AnchorEnd () - 1;
-        _eventLog.Y = 0;
-
-        _eventLog.Height = Dim.Height (_classListView);
-
-        //_eventLog.Width = 30;
 
         _layoutEditor.Width = Dim.Fill (
                                         Dim.Func (
@@ -194,7 +191,6 @@ public class AllViewsTester : Scenario
             Height = Dim.Fill (),
             CanFocus = true,
             TabStop = TabBehavior.TabStop,
-            //SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Base),
             Arrangement = ViewArrangement.LeftResizable | ViewArrangement.BottomResizable | ViewArrangement.RightResizable,
             BorderStyle = LineStyle.Double,
             SuperViewRendersLineCanvas = true
@@ -228,7 +224,7 @@ public class AllViewsTester : Scenario
         if (type.IsGenericType)
         {
             // For each of the <T> arguments
-            List<Type> typeArguments = new ();
+            List<Type> typeArguments = [];
 
             // use <object> or the original type if applicable
             foreach (Type arg in type.GetGenericArguments ())

+ 2 - 2
Examples/UICatalog/Scenarios/Bars.cs

@@ -185,9 +185,9 @@ public class Bars : Scenario
 
         menuLikeExamples.Add (popOverMenu);
 
-        menuLikeExamples.MouseClick += MenuLikeExamplesMouseClick;
+        menuLikeExamples.MouseEvent += MenuLikeExamplesMouseEvent;
 
-        void MenuLikeExamplesMouseClick (object sender, MouseEventArgs e)
+        void MenuLikeExamplesMouseEvent (object _, MouseEventArgs e)
         {
             if (e.Flags.HasFlag (MouseFlags.Button3Clicked))
             {

+ 22 - 49
Examples/UICatalog/Scenarios/Buttons.cs

@@ -243,17 +243,17 @@ public class Buttons : Scenario
         };
         main.Add (label);
 
-        var radioGroup = new RadioGroup
+        OptionSelector<Alignment> osAlignment = new ()
         {
             X = 4,
             Y = Pos.Bottom (label) + 1,
-            SelectedItem = 2,
-            RadioLabels = new [] { "_Start", "_End", "_Center", "_Fill" },
-            Title = "_9 RadioGroup",
+            Value = Alignment.Center,
+            AssignHotKeys = true,
+            Title = "_9 OptionSelector",
             BorderStyle = LineStyle.Dotted,
             // CanFocus = false
         };
-        main.Add (radioGroup);
+        main.Add (osAlignment);
 
         // Demo changing hotkey
         string MoveHotkey (string txt)
@@ -292,7 +292,7 @@ public class Buttons : Scenario
         var moveHotKeyBtn = new Button
         {
             X = 2,
-            Y = Pos.Bottom (radioGroup) + 1,
+            Y = Pos.Bottom (osAlignment) + 1,
             Width = Dim.Width (computedFrame) - 2,
             SchemeName = "TopLevel",
             Text = mhkb
@@ -309,7 +309,7 @@ public class Buttons : Scenario
         var moveUnicodeHotKeyBtn = new Button
         {
             X = Pos.Left (absoluteFrame) + 1,
-            Y = Pos.Bottom (radioGroup) + 1,
+            Y = Pos.Bottom (osAlignment) + 1,
             Width = Dim.Width (absoluteFrame) - 2,
             SchemeName = "TopLevel",
             Text = muhkb
@@ -321,48 +321,21 @@ public class Buttons : Scenario
                                        };
         main.Add (moveUnicodeHotKeyBtn);
 
-        radioGroup.SelectedItemChanged += (s, args) =>
-                                          {
-                                              switch (args.SelectedItem)
-                                              {
-                                                  case 0:
-                                                      moveBtn.TextAlignment = Alignment.Start;
-                                                      sizeBtn.TextAlignment = Alignment.Start;
-                                                      moveBtnA.TextAlignment = Alignment.Start;
-                                                      sizeBtnA.TextAlignment = Alignment.Start;
-                                                      moveHotKeyBtn.TextAlignment = Alignment.Start;
-                                                      moveUnicodeHotKeyBtn.TextAlignment = Alignment.Start;
-
-                                                      break;
-                                                  case 1:
-                                                      moveBtn.TextAlignment = Alignment.End;
-                                                      sizeBtn.TextAlignment = Alignment.End;
-                                                      moveBtnA.TextAlignment = Alignment.End;
-                                                      sizeBtnA.TextAlignment = Alignment.End;
-                                                      moveHotKeyBtn.TextAlignment = Alignment.End;
-                                                      moveUnicodeHotKeyBtn.TextAlignment = Alignment.End;
-
-                                                      break;
-                                                  case 2:
-                                                      moveBtn.TextAlignment = Alignment.Center;
-                                                      sizeBtn.TextAlignment = Alignment.Center;
-                                                      moveBtnA.TextAlignment = Alignment.Center;
-                                                      sizeBtnA.TextAlignment = Alignment.Center;
-                                                      moveHotKeyBtn.TextAlignment = Alignment.Center;
-                                                      moveUnicodeHotKeyBtn.TextAlignment = Alignment.Center;
-
-                                                      break;
-                                                  case 3:
-                                                      moveBtn.TextAlignment = Alignment.Fill;
-                                                      sizeBtn.TextAlignment = Alignment.Fill;
-                                                      moveBtnA.TextAlignment = Alignment.Fill;
-                                                      sizeBtnA.TextAlignment = Alignment.Fill;
-                                                      moveHotKeyBtn.TextAlignment = Alignment.Fill;
-                                                      moveUnicodeHotKeyBtn.TextAlignment = Alignment.Fill;
-
-                                                      break;
-                                              }
-                                          };
+        osAlignment.ValueChanged += (s, args) =>
+                                    {
+                                        if (args.Value is null)
+                                        {
+                                            return;
+                                        }
+
+                                        Alignment newValue = args.Value.Value;
+                                        moveBtn.TextAlignment = newValue;
+                                        sizeBtn.TextAlignment = newValue;
+                                        moveBtnA.TextAlignment = newValue;
+                                        sizeBtnA.TextAlignment = newValue;
+                                        moveHotKeyBtn.TextAlignment = newValue;
+                                        moveUnicodeHotKeyBtn.TextAlignment = newValue;
+                                    };
 
         label = new ()
         {

+ 6 - 30
Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs

@@ -365,40 +365,16 @@ public class CharacterMap : Scenario
         options [0] = "All";
         Array.Copy (allCategoryNames, 0, options, 1, allCategoryNames.Length);
 
-        // TODO: When #4126 is merged update this to use OptionSelector<UnicodeCategory?>
-        var selector = new OptionSelector
-        {
-            AssignHotKeysToCheckBoxes = true,
-            Options = options
-        };
+        // TODO: Add a "None" option
+        OptionSelector<UnicodeCategory> selector = new ();
 
         _unicodeCategorySelector = selector;
 
-        // Default to "All"
-        selector.SelectedItem = 0;
+        selector.Value = null;
         _charMap!.ShowUnicodeCategory = null;
 
-        selector.SelectedItemChanged += (s, e) =>
-                                        {
-                                            int? idx = selector.SelectedItem;
-
-                                            if (idx is null)
-                                            {
-                                                return;
-                                            }
-
-                                            if (idx.Value == 0)
-                                            {
-                                                _charMap.ShowUnicodeCategory = null;
-                                            }
-                                            else
-                                            {
-                                                // Map index to UnicodeCategory (offset by 1 because 0 is "All")
-                                                UnicodeCategory cat = Enum.GetValues<UnicodeCategory> () [idx.Value - 1];
-                                                _charMap.ShowUnicodeCategory = cat;
-                                            }
-                                        };
-
-        return new() { CommandView = selector };
+        selector.ValueChanged += (_, e) => _charMap.ShowUnicodeCategory = e.Value;
+
+        return new () { CommandView = selector };
     }
 }

+ 19 - 15
Examples/UICatalog/Scenarios/ColorPicker.cs

@@ -125,26 +125,25 @@ public class ColorPickers : Scenario
         app.Add (_demoView);
 
 
-        // Radio for switching color models
-        var rgColorModel = new RadioGroup ()
+        var osColorModel = new OptionSelector ()
         {
             Y = Pos.Bottom (_demoView),
             Width = Dim.Auto (),
             Height = Dim.Auto (),
-            RadioLabels = new []
-            {
+            Labels =
+            [
                 "_RGB",
                 "_HSV",
                 "H_SL",
                 "_16 Colors"
-            },
-            SelectedItem = (int)foregroundColorPicker.Style.ColorModel,
+            ],
+            Value = (int)foregroundColorPicker.Style.ColorModel,
         };
 
-        rgColorModel.SelectedItemChanged += (_, e) =>
+        osColorModel.ValueChanged += (_, e) =>
                                             {
                                                 // 16 colors
-                                                if (e.SelectedItem == 3)
+                                                if (e.Value == 3)
                                                 {
 
                                                     foregroundColorPicker16.Visible = true;
@@ -161,12 +160,17 @@ public class ColorPickers : Scenario
                                                 {
                                                     foregroundColorPicker16.Visible = false;
                                                     foregroundColorPicker.Visible = true;
-                                                    foregroundColorPicker.Style.ColorModel = (ColorModel)e.SelectedItem;
-                                                    foregroundColorPicker.ApplyStyleChanges ();
 
-                                                    backgroundColorPicker16.Visible = false;
-                                                    backgroundColorPicker.Visible = true;
-                                                    backgroundColorPicker.Style.ColorModel = (ColorModel)e.SelectedItem;
+                                                    if (e.Value is { })
+                                                    {
+                                                        foregroundColorPicker.Style.ColorModel = (ColorModel)e.Value;
+                                                        foregroundColorPicker.ApplyStyleChanges ();
+
+                                                        backgroundColorPicker16.Visible = false;
+                                                        backgroundColorPicker.Visible = true;
+                                                        backgroundColorPicker.Style.ColorModel = (ColorModel)e.Value;
+                                                    }
+
                                                     backgroundColorPicker.ApplyStyleChanges ();
 
 
@@ -176,13 +180,13 @@ public class ColorPickers : Scenario
                                                 }
                                             };
 
-        app.Add (rgColorModel);
+        app.Add (osColorModel);
 
         // Checkbox for switching show text fields on and off
         var cbShowTextFields = new CheckBox ()
         {
             Text = "Show _Text Fields",
-            Y = Pos.Bottom (rgColorModel) + 1,
+            Y = Pos.Bottom (osColorModel) + 1,
             Width = Dim.Auto (),
             Height = Dim.Auto (),
             CheckedState = foregroundColorPicker.Style.ShowTextFields ? CheckState.Checked : CheckState.UnChecked,

+ 7 - 10
Examples/UICatalog/Scenarios/Dialogs.cs

@@ -151,18 +151,15 @@ public class Dialogs : Scenario
         };
         frame.Add (label);
 
-        // Add hotkeys
-        var labels = Enum.GetNames<Alignment> ().Select (n => n = "_" + n);
-        var alignmentGroup = new RadioGroup
+        OptionSelector<Alignment> alignmentOptionSelector = new ()
         {
             X = Pos.Right (label) + 1,
             Y = Pos.Top (label),
-            RadioLabels = labels.ToArray (),
             Title = "Ali_gn",
-            BorderStyle = LineStyle.Dashed
+            AssignHotKeys = true
         };
-        frame.Add (alignmentGroup);
-        alignmentGroup.SelectedItem = labels.ToList ().IndexOf ("_" + Dialog.DefaultButtonAlignment.ToString ());
+        frame.Add (alignmentOptionSelector);
+        alignmentOptionSelector.Value = Dialog.DefaultButtonAlignment;
 
         frame.ValidatePosDim = true;
 
@@ -192,7 +189,7 @@ public class Dialogs : Scenario
                                                                       titleEdit,
                                                                       numButtonsEdit,
                                                                       glyphsNotWords,
-                                                                      alignmentGroup,
+                                                                      alignmentOptionSelector,
                                                                       buttonPressedLabel
                                                                      );
                                        Application.Run (dlg);
@@ -216,7 +213,7 @@ public class Dialogs : Scenario
         TextField titleEdit,
         TextField numButtonsEdit,
         CheckBox glyphsNotWords,
-        RadioGroup alignmentRadioGroup,
+        OptionSelector alignmentGroup,
         Label buttonPressedLabel
     )
     {
@@ -269,7 +266,7 @@ public class Dialogs : Scenario
             {
                 Title = titleEdit.Text,
                 Text = "Dialog Text",
-                ButtonAlignment = (Alignment)Enum.Parse (typeof (Alignment), alignmentRadioGroup.RadioLabels [alignmentRadioGroup.SelectedItem].Substring (1)),
+                ButtonAlignment = (Alignment)Enum.Parse (typeof (Alignment), alignmentGroup.Labels! [(int)alignmentGroup.Value!.Value] [1..]),
 
                 Buttons = buttons.ToArray ()
             };

+ 4 - 4
Examples/UICatalog/Scenarios/DimAutoDemo.cs

@@ -163,15 +163,15 @@ public class DimAutoDemo : Scenario
         dimAutoFrameView.Add (resetButton);
 
 
-        var radioGroup = new RadioGroup ()
+        var optionSelector = new OptionSelector ()
         {
-            RadioLabels = ["One", "Two", "Three"],
+            Labels = ["One", "Two", "Three"],
             X = 0,
             Y = Pos.AnchorEnd (),
-            Title = "Radios",
+            Title = "Options",
             BorderStyle = LineStyle.Dotted
         };
-        dimAutoFrameView.Add (radioGroup);
+        dimAutoFrameView.Add (optionSelector);
         return dimAutoFrameView;
     }
 

+ 10 - 10
Examples/UICatalog/Scenarios/DynamicMenuBar.cs

@@ -174,11 +174,11 @@ public class DynamicMenuBar : Scenario
 
             var rChkLabels = new [] { "NoCheck", "Checked", "Radio" };
 
-            RbChkStyle = new ()
+            OsChkStyle = new ()
             {
-                X = Pos.Left (lblTitle), Y = Pos.Bottom (CkbSubMenu) + 1, RadioLabels = rChkLabels
+                X = Pos.Left (lblTitle), Y = Pos.Bottom (CkbSubMenu) + 1, Labels = rChkLabels
             };
-            Add (RbChkStyle);
+            Add (OsChkStyle);
 
             var lblShortcut = new Label
             {
@@ -294,7 +294,7 @@ public class DynamicMenuBar : Scenario
         public CheckBox CkbIsTopLevel { get; }
         public CheckBox CkbNullCheck { get; }
         public CheckBox CkbSubMenu { get; }
-        public RadioGroup RbChkStyle { get; }
+        public OptionSelector OsChkStyle { get; }
         public TextView TextAction { get; }
         public TextField TextHelp { get; }
         public TextField TextHotKey { get; }
@@ -361,7 +361,7 @@ public class DynamicMenuBar : Scenario
             CkbNullCheck.CheckedState = menuItem.AllowNullChecked ? CheckState.Checked : CheckState.UnChecked;
             TextHelp.Enabled = CkbSubMenu.CheckedState == CheckState.UnChecked;
             TextAction.Enabled = CkbSubMenu.CheckedState == CheckState.UnChecked;
-            RbChkStyle.SelectedItem = (int)(menuItem?.CheckType ?? MenuItemCheckStyle.NoCheck);
+            OsChkStyle.Value = (int)(menuItem?.CheckType ?? MenuItemCheckStyle.NoCheck);
             TextShortcutKey.Text = menuItem?.ShortcutTag ?? "";
 
             TextShortcutKey.Enabled = CkbIsTopLevel.CheckedState == CheckState.Checked && CkbSubMenu.CheckedState == CheckState.UnChecked
@@ -434,8 +434,8 @@ public class DynamicMenuBar : Scenario
                     HotKey = TextHotKey.Text,
                     IsTopLevel = CkbIsTopLevel?.CheckedState == CheckState.Checked,
                     HasSubMenu = CkbSubMenu?.CheckedState == CheckState.Checked,
-                    CheckStyle = RbChkStyle.SelectedItem == 0 ? MenuItemCheckStyle.NoCheck :
-                                 RbChkStyle.SelectedItem == 1 ? MenuItemCheckStyle.Checked :
+                    CheckStyle = OsChkStyle.Value == 0 ? MenuItemCheckStyle.NoCheck :
+                                 OsChkStyle.Value == 1 ? MenuItemCheckStyle.Checked :
                                  MenuItemCheckStyle.Radio,
                     ShortcutKey = TextShortcutKey.Text,
                     AllowNullChecked = CkbNullCheck?.CheckedState == CheckState.Checked,
@@ -484,7 +484,7 @@ public class DynamicMenuBar : Scenario
             TextHotKey.Text = "";
             CkbIsTopLevel.CheckedState = CheckState.UnChecked;
             CkbSubMenu.CheckedState = CheckState.UnChecked;
-            RbChkStyle.SelectedItem = (int)MenuItemCheckStyle.NoCheck;
+            OsChkStyle.Value = (int)MenuItemCheckStyle.NoCheck;
             TextShortcutKey.Text = "";
         }
 
@@ -829,9 +829,9 @@ public class DynamicMenuBar : Scenario
                                          HotKey = frmMenuDetails.TextHotKey.Text,
                                          IsTopLevel = frmMenuDetails.CkbIsTopLevel?.CheckedState == CheckState.Checked,
                                          HasSubMenu = frmMenuDetails.CkbSubMenu?.CheckedState == CheckState.Checked,
-                                         CheckStyle = frmMenuDetails.RbChkStyle.SelectedItem == 0
+                                         CheckStyle = frmMenuDetails.OsChkStyle.Value == 0
                                                           ? MenuItemCheckStyle.NoCheck
-                                                          : frmMenuDetails.RbChkStyle.SelectedItem == 1
+                                                          : frmMenuDetails.OsChkStyle.Value == 1
                                                               ? MenuItemCheckStyle.Checked
                                                               : MenuItemCheckStyle.Radio,
                                          ShortcutKey = frmMenuDetails.TextShortcutKey.Text

+ 13 - 79
Examples/UICatalog/Scenarios/EditorsAndHelpers/ArrangementEditor.cs

@@ -1,7 +1,4 @@
 #nullable enable
-using System;
-using System.Collections.Generic;
-
 namespace UICatalog.Scenarios;
 
 /// <summary>
@@ -16,92 +13,34 @@ public sealed class ArrangementEditor : EditorBase
 
         Initialized += ArrangementEditor_Initialized;
 
-        _arrangementSlider.Options =
-        [
-            new SliderOption<ViewArrangement>
-            {
-                Legend = ViewArrangement.Movable.ToString (),
-                Data = ViewArrangement.Movable
-            },
-
-            new SliderOption<ViewArrangement>
-            {
-                Legend = ViewArrangement.LeftResizable.ToString (),
-                Data = ViewArrangement.LeftResizable
-            },
-
-            new SliderOption<ViewArrangement>
-            {
-                Legend = ViewArrangement.RightResizable.ToString (),
-                Data = ViewArrangement.RightResizable
-            },
-
-            new SliderOption<ViewArrangement>
-            {
-                Legend = ViewArrangement.TopResizable.ToString (),
-                Data = ViewArrangement.TopResizable
-            },
-
-            new SliderOption<ViewArrangement>
-            {
-                Legend = ViewArrangement.BottomResizable.ToString (),
-                Data = ViewArrangement.BottomResizable
-            },
-
-            new SliderOption<ViewArrangement>
-            {
-                Legend = ViewArrangement.Overlapped.ToString (),
-                Data = ViewArrangement.Overlapped
-            }
-        ];
-
-        Add (_arrangementSlider);
+        Add (_arrangementSelector);
     }
 
-    private readonly Slider<ViewArrangement> _arrangementSlider = new()
+    private readonly FlagSelector<ViewArrangement> _arrangementSelector = new ()
     {
-        Orientation = Orientation.Vertical,
-        UseMinimumSize = true,
-        Type = SliderType.Multiple,
-        AllowEmpty = true,
+        Orientation = Orientation.Vertical
     };
 
     protected override void OnViewToEditChanged ()
     {
-        _arrangementSlider.Enabled = ViewToEdit is not Adornment;
+        _arrangementSelector.Enabled = ViewToEdit is not Adornment;
 
-        _arrangementSlider.OptionsChanged -= ArrangementSliderOnOptionsChanged;
+        _arrangementSelector.ValueChanged -= ArrangementFlagsOnValueChanged;
 
         // Set the appropriate options in the slider based on _viewToEdit.Arrangement
         if (ViewToEdit is { })
         {
-            _arrangementSlider.Options.ForEach (
-                                                option =>
-                                                {
-                                                    _arrangementSlider.ChangeOption (
-                                                                                     _arrangementSlider.Options.IndexOf (option),
-                                                                                     (ViewToEdit.Arrangement & option.Data) == option.Data);
-                                                });
+            _arrangementSelector.Value = ViewToEdit.Arrangement;
         }
 
-        _arrangementSlider.OptionsChanged += ArrangementSliderOnOptionsChanged;
+        _arrangementSelector.ValueChanged += ArrangementFlagsOnValueChanged;
     }
 
-    private void ArrangementEditor_Initialized (object? sender, EventArgs e) { _arrangementSlider.OptionsChanged += ArrangementSliderOnOptionsChanged; }
-
-    private void ArrangementSliderOnOptionsChanged (object? sender, SliderEventArgs<ViewArrangement> e)
+    private void ArrangementFlagsOnValueChanged (object? sender, EventArgs<ViewArrangement?> e)
     {
-        if (ViewToEdit is { })
+        if (ViewToEdit is { } && e.Value is { })
         {
-            // Set the arrangement based on the selected options
-            var arrangement = ViewArrangement.Fixed;
-
-            foreach (KeyValuePair<int, SliderOption<ViewArrangement>> option in e.Options)
-            {
-                arrangement |= option.Value.Data;
-            }
-
-            ViewToEdit.Arrangement = arrangement;
+            ViewToEdit.Arrangement = (ViewArrangement)e.Value;
 
             if (ViewToEdit.Arrangement.HasFlag (ViewArrangement.Overlapped))
             {
@@ -114,14 +53,9 @@ public sealed class ArrangementEditor : EditorBase
                 ViewToEdit.SchemeName = ViewToEdit!.SuperView!.SchemeName;
             }
 
-            if (ViewToEdit.Arrangement.HasFlag (ViewArrangement.Movable))
-            {
-                ViewToEdit.BorderStyle = LineStyle.Double;
-            }
-            else
-            {
-                ViewToEdit.BorderStyle = LineStyle.Single;
-            }
+            ViewToEdit.BorderStyle = ViewToEdit.Arrangement.HasFlag (ViewArrangement.Movable) ? LineStyle.Double : LineStyle.Single;
         }
     }
+
+    private void ArrangementEditor_Initialized (object? sender, EventArgs e) { _arrangementSelector.ValueChanged += ArrangementFlagsOnValueChanged; }
 }

+ 14 - 13
Examples/UICatalog/Scenarios/EditorsAndHelpers/BorderEditor.cs

@@ -8,7 +8,7 @@ namespace UICatalog.Scenarios;
 public class BorderEditor : AdornmentEditor
 {
     private CheckBox? _ckbTitle;
-    private RadioGroup? _rbBorderStyle;
+    private OptionSelector<LineStyle>? _osBorderStyle;
     private CheckBox? _ckbGradient;
 
     public BorderEditor ()
@@ -21,34 +21,31 @@ public class BorderEditor : AdornmentEditor
     private void BorderEditor_AdornmentChanged (object? sender, EventArgs e)
     {
         _ckbTitle!.CheckedState = ((Border)AdornmentToEdit!).Settings.FastHasFlags (BorderSettings.Title) ? CheckState.Checked : CheckState.UnChecked;
-        _rbBorderStyle!.SelectedItem = (int)((Border)AdornmentToEdit).LineStyle;
+        _osBorderStyle!.Value = ((Border)AdornmentToEdit).LineStyle;
         _ckbGradient!.CheckedState = ((Border)AdornmentToEdit).Settings.FastHasFlags (BorderSettings.Gradient) ? CheckState.Checked : CheckState.UnChecked;
     }
 
     private void BorderEditor_Initialized (object? sender, EventArgs e)
     {
-        List<LineStyle> borderStyleEnum = Enum.GetValues (typeof (LineStyle)).Cast<LineStyle> ().ToList ();
-
-        _rbBorderStyle = new ()
+        _osBorderStyle = new ()
         {
             X = 0,
 
-            Y = Pos.Bottom (SubViews.ToArray() [^1]),
+            Y = Pos.Bottom (SubViews.ToArray () [^1]),
             Width = Dim.Fill (),
-            SelectedItem = (int)(((Border)AdornmentToEdit!)?.LineStyle ?? LineStyle.None),
+            Value = ((Border)AdornmentToEdit!)?.LineStyle ?? LineStyle.None,
             BorderStyle = LineStyle.Single,
             Title = "Border St_yle",
             SuperViewRendersLineCanvas = true,
-            RadioLabels = borderStyleEnum.Select (style => style.ToString ()).ToArray ()
         };
-        Add (_rbBorderStyle);
+        Add (_osBorderStyle);
 
-        _rbBorderStyle.SelectedItemChanged += OnRbBorderStyleOnSelectedItemChanged;
+        _osBorderStyle.ValueChanged += OnRbBorderStyleOnValueChanged;
 
         _ckbTitle = new ()
         {
             X = 0,
-            Y = Pos.Bottom (_rbBorderStyle),
+            Y = Pos.Bottom (_osBorderStyle),
 
             CheckedState = CheckState.Checked,
             SuperViewRendersLineCanvas = true,
@@ -73,10 +70,14 @@ public class BorderEditor : AdornmentEditor
 
         return;
 
-        void OnRbBorderStyleOnSelectedItemChanged (object? s, SelectedItemChangedArgs args)
+        void OnRbBorderStyleOnValueChanged (object? s, EventArgs<LineStyle?> args)
         {
             LineStyle prevBorderStyle = AdornmentToEdit!.BorderStyle;
-            ((Border)AdornmentToEdit).LineStyle = (LineStyle)args.SelectedItem!;
+
+            if (args.Value is { })
+            {
+                ((Border)AdornmentToEdit).LineStyle = (LineStyle)args.Value;
+            }
 
             if (((Border)AdornmentToEdit).LineStyle == LineStyle.None)
             {

+ 9 - 9
Examples/UICatalog/Scenarios/EditorsAndHelpers/DimEditor.cs

@@ -18,7 +18,7 @@ public class DimEditor : EditorBase
     }
 
     private int _value;
-    private RadioGroup? _dimRadioGroup;
+    private OptionSelector? _dimOptionSelector;
     private TextField? _valueEdit;
 
     /// <inheritdoc />
@@ -44,7 +44,7 @@ public class DimEditor : EditorBase
 
         try
         {
-            _dimRadioGroup!.SelectedItem = _dimNames.IndexOf (_dimNames.First (s => dim!.ToString ().StartsWith (s)));
+            _dimOptionSelector!.Value = _dimNames.IndexOf (_dimNames.First (s => dim!.ToString ().StartsWith (s)));
         }
         catch (InvalidOperationException e)
         {
@@ -92,13 +92,13 @@ public class DimEditor : EditorBase
             Text = $"{Title}:"
         };
         Add (label);
-        _dimRadioGroup = new () { X = 0, Y = Pos.Bottom (label), RadioLabels = _radioItems };
-        _dimRadioGroup.SelectedItemChanged += OnRadioGroupOnSelectedItemChanged;
+        _dimOptionSelector = new () { X = 0, Y = Pos.Bottom (label), Labels = _optionLabels };
+        _dimOptionSelector.ValueChanged += OnOptionSelectorOnValueChanged;
         _valueEdit = new ()
         {
             X = Pos.Right (label) + 1,
             Y = 0,
-            Width = Dim.Func (_ => _radioItems.Max (i => i.GetColumns ()) - label.Frame.Width + 1),
+            Width = Dim.Func (_ => _optionLabels.Max (i => i.GetColumns ()) - label.Frame.Width + 1),
             Text = $"{_value}"
         };
 
@@ -117,15 +117,15 @@ public class DimEditor : EditorBase
         };
         Add (_valueEdit);
 
-        Add (_dimRadioGroup);
+        Add (_dimOptionSelector);
 
     }
 
-    private void OnRadioGroupOnSelectedItemChanged (object? s, SelectedItemChangedArgs selected) { DimChanged (); }
+    private void OnOptionSelectorOnValueChanged (object? s, EventArgs<int?> selected) { DimChanged (); }
 
     // These need to have same order 
     private readonly List<string> _dimNames = ["Absolute", "Auto", "Fill", "Func", "Percent",];
-    private readonly string [] _radioItems = ["Absolute(n)", "Auto", "Fill(n)", "Func(()=>n)", "Percent(n)",];
+    private readonly string [] _optionLabels = ["Absolute(n)", "Auto", "Fill(n)", "Func(()=>n)", "Percent(n)",];
 
     private void DimChanged ()
     {
@@ -136,7 +136,7 @@ public class DimEditor : EditorBase
 
         try
         {
-            Dim? dim = _dimRadioGroup!.SelectedItem switch
+            Dim? dim = _dimOptionSelector!.Value switch
             {
                 0 => Dim.Absolute (_value),
                 1 => Dim.Auto (),

+ 14 - 22
Examples/UICatalog/Scenarios/EditorsAndHelpers/MarginEditor.cs

@@ -12,7 +12,7 @@ public class MarginEditor : AdornmentEditor
         AdornmentChanged += MarginEditor_AdornmentChanged;
     }
 
-    private RadioGroup? _rgShadow;
+    private OptionSelector<ShadowStyle>? _optionsShadow;
 
     private FlagSelector? _flagSelectorTransparent;
 
@@ -20,18 +20,18 @@ public class MarginEditor : AdornmentEditor
     {
         if (AdornmentToEdit is { })
         {
-            _rgShadow!.SelectedItem = (int)((Margin)AdornmentToEdit).ShadowStyle;
+            _optionsShadow!.Value = ((Margin)AdornmentToEdit).ShadowStyle;
         }
 
         if (AdornmentToEdit is { })
         {
-            _flagSelectorTransparent!.Value = (uint)((Margin)AdornmentToEdit).ViewportSettings;
+            _flagSelectorTransparent!.Value = (int)((Margin)AdornmentToEdit).ViewportSettings;
         }
     }
 
     private void MarginEditor_Initialized (object? sender, EventArgs e)
     {
-        _rgShadow = new RadioGroup
+        _optionsShadow = new ()
         {
             X = 0,
             Y = Pos.Bottom (SubViews.ElementAt(SubViews.Count-1)),
@@ -39,44 +39,36 @@ public class MarginEditor : AdornmentEditor
             SuperViewRendersLineCanvas = true,
             Title = "_Shadow",
             BorderStyle = LineStyle.Single,
-            RadioLabels = Enum.GetNames (typeof (ShadowStyle)),
+            AssignHotKeys = true
         };
 
         if (AdornmentToEdit is { })
         {
-            _rgShadow.SelectedItem = (int)((Margin)AdornmentToEdit).ShadowStyle;
+            _optionsShadow.Value = ((Margin)AdornmentToEdit).ShadowStyle;
         }
 
-        _rgShadow.SelectedItemChanged += (_, args) =>
-                                        {
-                                            ((Margin)AdornmentToEdit!).ShadowStyle = (ShadowStyle)args.SelectedItem!;
-                                        };
+        _optionsShadow.ValueChanged += (_, args) => ((Margin)AdornmentToEdit!).ShadowStyle = args.Value!.Value;
 
-        Add (_rgShadow);
+        Add (_optionsShadow);
 
-        var flags = new Dictionary<uint, string> ()
-        {
-            { (uint)Terminal.Gui.ViewBase.ViewportSettingsFlags.Transparent, "Transparent" },
-            { (uint)Terminal.Gui.ViewBase.ViewportSettingsFlags.TransparentMouse, "TransparentMouse" }
-        };
-
-        _flagSelectorTransparent = new FlagSelector ()
+        _flagSelectorTransparent = new FlagSelector<ViewportSettingsFlags> ()
         {
             X = 0,
-            Y = Pos.Bottom (_rgShadow),
+            Y = Pos.Bottom (_optionsShadow),
 
             SuperViewRendersLineCanvas = true,
             Title = "_ViewportSettings",
             BorderStyle = LineStyle.Single,
         };
-        _flagSelectorTransparent.SetFlags(flags.AsReadOnly ());
-
+        _flagSelectorTransparent.Values = [(int)ViewportSettingsFlags.Transparent, (int)ViewportSettingsFlags.TransparentMouse];
+        _flagSelectorTransparent.Labels = ["Transparent", "TransparentMouse"];
+        _flagSelectorTransparent.AssignHotKeys = true;
 
         Add (_flagSelectorTransparent);
 
         if (AdornmentToEdit is { })
         {
-            _flagSelectorTransparent.Value = (uint)((Margin)AdornmentToEdit).ViewportSettings;
+            _flagSelectorTransparent.Value = (int)((Margin)AdornmentToEdit).ViewportSettings;
         }
 
         _flagSelectorTransparent.ValueChanged += (_, args) =>

+ 9 - 9
Examples/UICatalog/Scenarios/EditorsAndHelpers/PosEditor.cs

@@ -19,7 +19,7 @@ public class PosEditor : EditorBase
     }
 
     private int _value;
-    private RadioGroup? _posRadioGroup;
+    private OptionSelector? _posOptionSelector;
     private TextField? _valueEdit;
 
     protected override void OnUpdateLayoutSettings ()
@@ -44,7 +44,7 @@ public class PosEditor : EditorBase
 
         try
         {
-            _posRadioGroup!.SelectedItem = _posNames.IndexOf (_posNames.First (s => pos.ToString ().Contains (s)));
+            _posOptionSelector!.Value = _posNames.IndexOf (_posNames.First (s => pos.ToString ().Contains (s)));
         }
         catch (InvalidOperationException e)
         {
@@ -91,14 +91,14 @@ public class PosEditor : EditorBase
             Text = $"{Title}:"
         };
         Add (label);
-        _posRadioGroup = new () { X = 0, Y = Pos.Bottom (label), RadioLabels = _radioItems };
-        _posRadioGroup.SelectedItemChanged += OnRadioGroupOnSelectedItemChanged;
+        _posOptionSelector = new () { X = 0, Y = Pos.Bottom (label), Labels = _optionLabels };
+        _posOptionSelector.ValueChanged += OnOptionSelectorOnValueChanged;
 
         _valueEdit = new ()
         {
             X = Pos.Right (label) + 1,
             Y = 0,
-            Width = Dim.Func (_ => _radioItems.Max (i => i.GetColumns ()) - label.Frame.Width + 1),
+            Width = Dim.Func (_ => _optionLabels.Max (i => i.GetColumns ()) - label.Frame.Width + 1),
             Text = $"{_value}"
         };
 
@@ -118,14 +118,14 @@ public class PosEditor : EditorBase
                                 };
         Add (_valueEdit);
 
-        Add (_posRadioGroup);
+        Add (_posOptionSelector);
     }
 
-    private void OnRadioGroupOnSelectedItemChanged (object? s, SelectedItemChangedArgs selected) { PosChanged (); }
+    private void OnOptionSelectorOnValueChanged (object? s, EventArgs<int?> selected) { PosChanged (); }
 
     // These need to have same order 
     private readonly List<string> _posNames = ["Absolute", "Align", "AnchorEnd", "Center", "Func", "Percent"];
-    private readonly string [] _radioItems = ["Absolute(n)", "Align", "AnchorEnd", "Center", "Func(()=>n)", "Percent(n)"];
+    private readonly string [] _optionLabels = ["Absolute(n)", "Align", "AnchorEnd", "Center", "Func(()=>n)", "Percent(n)"];
 
     private void PosChanged ()
     {
@@ -136,7 +136,7 @@ public class PosEditor : EditorBase
 
         try
         {
-            Pos? pos = _posRadioGroup!.SelectedItem switch
+            Pos? pos = _posOptionSelector!.Value switch
                        {
                            0 => Pos.Absolute (_value),
                            1 => Pos.Align (Alignment.Start),

+ 9 - 10
Examples/UICatalog/Scenarios/EditorsAndHelpers/ViewPropertiesEditor.cs

@@ -6,7 +6,7 @@ public class ViewPropertiesEditor : EditorBase
 {
     private CheckBox? _canFocusCheckBox;
     private CheckBox? _enabledCheckBox;
-    private RadioGroup? _orientation;
+    private OptionSelector<Orientation>? _orientationOptionSelector;
     private TextView? _text;
 
     /// <inheritdoc/>
@@ -48,24 +48,23 @@ public class ViewPropertiesEditor : EditorBase
 
         Label label = new () { X = Pos.Right (_enabledCheckBox) + 1, Y = Pos.Top (_enabledCheckBox), Text = "Orientation:" };
 
-        _orientation = new ()
+        _orientationOptionSelector = new ()
         {
             X = Pos.Right (label) + 1,
             Y = Pos.Top (label),
-            RadioLabels = new [] { "Horizontal", "Vertical" },
             Orientation = Orientation.Horizontal
         };
 
-        _orientation.SelectedItemChanged += (s, selected) =>
+        _orientationOptionSelector.ValueChanged += (s, selected) =>
                                             {
                                                 if (ViewToEdit is IOrientation orientatedView)
                                                 {
-                                                    orientatedView.Orientation = (Orientation)_orientation.SelectedItem!;
+                                                    orientatedView.Orientation = _orientationOptionSelector.Value!.Value;
                                                 }
                                             };
-        Add (label, _orientation);
+        Add (label, _orientationOptionSelector);
 
-        label = new () { X = 0, Y = Pos.Bottom (_orientation), Text = "Text:" };
+        label = new () { X = 0, Y = Pos.Bottom (_orientationOptionSelector), Text = "Text:" };
 
         _text = new ()
         {
@@ -114,12 +113,12 @@ public class ViewPropertiesEditor : EditorBase
 
             if (ViewToEdit is IOrientation orientatedView)
             {
-                _orientation!.SelectedItem = (int)orientatedView.Orientation;
-                _orientation.Enabled = true;
+                _orientationOptionSelector!.Value = orientatedView.Orientation;
+                _orientationOptionSelector.Enabled = true;
             }
             else
             {
-                _orientation!.Enabled = false;
+                _orientationOptionSelector!.Enabled = false;
             }
         }
     }

+ 133 - 133
Examples/UICatalog/Scenarios/FileDialogExamples.cs

@@ -1,8 +1,4 @@
-using System;
-using System.IO;
-using System.IO.Abstractions;
-using System.Linq;
-using System.Text;
+using System.IO.Abstractions;
 
 namespace UICatalog.Scenarios;
 
@@ -20,10 +16,10 @@ public class FileDialogExamples : Scenario
     private CheckBox _cbMustExist;
     private CheckBox _cbShowTreeBranchLines;
     private CheckBox _cbUseColors;
-    private RadioGroup _rgAllowedTypes;
-    private RadioGroup _rgCaption;
-    private RadioGroup _rgIcons;
-    private RadioGroup _rgOpenMode;
+    private OptionSelector _osAllowedTypes;
+    private OptionSelector _osCaption;
+    private OptionSelector _osIcons;
+    private OptionSelector _osOpenMode;
     private TextField _tbCancelButton;
     private TextField _tbOkButton;
 
@@ -34,28 +30,29 @@ public class FileDialogExamples : Scenario
         var x = 1;
         var win = new Window { Title = GetQuitKeyAndName () };
 
-        _cbMustExist = new CheckBox { CheckedState = CheckState.Checked, Y = y++, X = x, Text = "Must E_xist" };
+        _cbMustExist = new () { CheckedState = CheckState.Checked, Y = y++, X = x, Text = "Must E_xist" };
         win.Add (_cbMustExist);
 
-        _cbUseColors = new CheckBox { CheckedState = FileDialogStyle.DefaultUseColors ? CheckState.Checked : CheckState.UnChecked, Y = y++, X = x, Text = "_Use Colors" };
+        _cbUseColors = new ()
+            { CheckedState = FileDialogStyle.DefaultUseColors ? CheckState.Checked : CheckState.UnChecked, Y = y++, X = x, Text = "_Use Colors" };
         win.Add (_cbUseColors);
 
-        _cbCaseSensitive = new CheckBox { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "_Case Sensitive Search" };
+        _cbCaseSensitive = new () { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "_Case Sensitive Search" };
         win.Add (_cbCaseSensitive);
 
-        _cbAllowMultipleSelection = new CheckBox { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "_Multiple" };
+        _cbAllowMultipleSelection = new () { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "_Multiple" };
         win.Add (_cbAllowMultipleSelection);
 
-        _cbShowTreeBranchLines = new CheckBox { CheckedState = CheckState.Checked, Y = y++, X = x, Text = "Tree Branch _Lines" };
+        _cbShowTreeBranchLines = new () { CheckedState = CheckState.Checked, Y = y++, X = x, Text = "Tree Branch _Lines" };
         win.Add (_cbShowTreeBranchLines);
 
-        _cbAlwaysTableShowHeaders = new CheckBox { CheckedState = CheckState.Checked, Y = y++, X = x, Text = "Always Show _Headers" };
+        _cbAlwaysTableShowHeaders = new () { CheckedState = CheckState.Checked, Y = y++, X = x, Text = "Always Show _Headers" };
         win.Add (_cbAlwaysTableShowHeaders);
 
-        _cbDrivesOnlyInTree = new CheckBox { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "Only Show _Drives" };
+        _cbDrivesOnlyInTree = new () { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "Only Show _Drives" };
         win.Add (_cbDrivesOnlyInTree);
 
-        _cbPreserveFilenameOnDirectoryChanges = new CheckBox { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "Preserve Filename" };
+        _cbPreserveFilenameOnDirectoryChanges = new () { CheckedState = CheckState.UnChecked, Y = y++, X = x, Text = "Preserve Filename" };
         win.Add (_cbPreserveFilenameOnDirectoryChanges);
 
         y = 0;
@@ -66,9 +63,9 @@ public class FileDialogExamples : Scenario
                 );
         win.Add (new Label { X = x++, Y = y++, Text = "Caption" });
 
-        _rgCaption = new RadioGroup { X = x, Y = y };
-        _rgCaption.RadioLabels = new [] { "_Ok", "O_pen", "_Save" };
-        win.Add (_rgCaption);
+        _osCaption = new () { X = x, Y = y };
+        _osCaption.Labels = ["_Ok", "O_pen", "_Save"];
+        win.Add (_osCaption);
 
         y = 0;
         x = 34;
@@ -78,9 +75,9 @@ public class FileDialogExamples : Scenario
                 );
         win.Add (new Label { X = x++, Y = y++, Text = "OpenMode" });
 
-        _rgOpenMode = new RadioGroup { X = x, Y = y };
-        _rgOpenMode.RadioLabels = new [] { "_File", "D_irectory", "_Mixed" };
-        win.Add (_rgOpenMode);
+        _osOpenMode = new () { X = x, Y = y };
+        _osOpenMode.Labels = ["_File", "D_irectory", "_Mixed"];
+        win.Add (_osOpenMode);
 
         y = 0;
         x = 48;
@@ -90,9 +87,9 @@ public class FileDialogExamples : Scenario
                 );
         win.Add (new Label { X = x++, Y = y++, Text = "Icons" });
 
-        _rgIcons = new RadioGroup { X = x, Y = y };
-        _rgIcons.RadioLabels = new [] { "_None", "_Unicode", "Nerd_*" };
-        win.Add (_rgIcons);
+        _osIcons = new () { X = x, Y = y };
+        _osIcons.Labels = ["_None", "_Unicode", "Nerd_*"];
+        win.Add (_osIcons);
 
         win.Add (new Label { Y = Pos.AnchorEnd (2), Text = "* Requires installing Nerd fonts" });
         win.Add (new Label { Y = Pos.AnchorEnd (1), Text = "  (see: https://github.com/devblackops/Terminal-Icons)" });
@@ -105,9 +102,9 @@ public class FileDialogExamples : Scenario
                 );
         win.Add (new Label { X = x++, Y = y++, Text = "Allowed" });
 
-        _rgAllowedTypes = new RadioGroup { X = x, Y = y };
-        _rgAllowedTypes.RadioLabels = new [] { "An_y", "Cs_v (Recommended)", "Csv (S_trict)" };
-        win.Add (_rgAllowedTypes);
+        _osAllowedTypes = new () { X = x, Y = y };
+        _osAllowedTypes.Labels = ["An_y", "Cs_v (Recommended)", "Csv (S_trict)"];
+        win.Add (_osAllowedTypes);
 
         y = 5;
         x = 45;
@@ -118,31 +115,31 @@ public class FileDialogExamples : Scenario
         win.Add (new Label { X = x++, Y = y++, Text = "Buttons" });
 
         win.Add (new Label { X = x, Y = y++, Text = "O_k Text:" });
-        _tbOkButton = new TextField { X = x, Y = y++, Width = 12 };
+        _tbOkButton = new () { X = x, Y = y++, Width = 12 };
         win.Add (_tbOkButton);
         win.Add (new Label { X = x, Y = y++, Text = "_Cancel Text:" });
-        _tbCancelButton = new TextField { X = x, Y = y++, Width = 12 };
+        _tbCancelButton = new () { X = x, Y = y++, Width = 12 };
         win.Add (_tbCancelButton);
-        _cbFlipButtonOrder = new CheckBox { X = x, Y = y++, Text = "Flip Ord_er" };
+        _cbFlipButtonOrder = new () { X = x, Y = y++, Text = "Flip Ord_er" };
         win.Add (_cbFlipButtonOrder);
 
         var btn = new Button { X = 1, Y = 9, IsDefault = true, Text = "Run Dialog" };
 
         win.Accepting += (s, e) =>
-                        {
-                            try
-                            {
-                                CreateDialog ();
-                            }
-                            catch (Exception ex)
-                            {
-                                MessageBox.ErrorQuery ("Error", ex.ToString (), "_Ok");
-                            }
-                            finally
-                            {
-                                e.Handled = true;
-                            }
-                        };
+                         {
+                             try
+                             {
+                                 CreateDialog ();
+                             }
+                             catch (Exception ex)
+                             {
+                                 MessageBox.ErrorQuery ("Error", ex.ToString (), "_Ok");
+                             }
+                             finally
+                             {
+                                 e.Handled = true;
+                             }
+                         };
         win.Add (btn);
 
         Application.Run (win);
@@ -164,112 +161,115 @@ public class FileDialogExamples : Scenario
 
     private void CreateDialog ()
     {
-        var fd = new FileDialog
+        if (_osOpenMode.Value is { })
         {
-            OpenMode = Enum.Parse<OpenMode> (
-                                             _rgOpenMode.RadioLabels
-                                                        .Select (l => TextFormatter.FindHotKey (l, _rgOpenMode.HotKeySpecifier, out int hotPos, out Key _)
-
-                                                                          // Remove the hotkey specifier at the found position
-                                                                          ? TextFormatter.RemoveHotKeySpecifier (l, hotPos, _rgOpenMode.HotKeySpecifier)
-
-                                                                          // No hotkey found, return the label as is
-                                                                          : l)
-                                                        .ToArray () [_rgOpenMode.SelectedItem]
-                                            ),
-            MustExist = _cbMustExist.CheckedState == CheckState.Checked,
-            AllowsMultipleSelection = _cbAllowMultipleSelection.CheckedState == CheckState.Checked
-        };
+            var fd = new FileDialog
+            {
+                OpenMode = Enum.Parse<OpenMode> (
+                                                 _osOpenMode.Labels
+                                                            .Select (l => TextFormatter.FindHotKey (l, _osOpenMode.HotKeySpecifier, out int hotPos, out Key _)
+
+                                                                              // Remove the hotkey specifier at the found position
+                                                                              ? TextFormatter.RemoveHotKeySpecifier (l, hotPos, _osOpenMode.HotKeySpecifier)
+
+                                                                              // No hotkey found, return the label as is
+                                                                              : l)
+                                                            .ToArray () [_osOpenMode.Value.Value]
+                                                ),
+                MustExist = _cbMustExist.CheckedState == CheckState.Checked,
+                AllowsMultipleSelection = _cbAllowMultipleSelection.CheckedState == CheckState.Checked
+            };
+
+            fd.Style.OkButtonText =
+                _osCaption.Labels.Select (l => TextFormatter.RemoveHotKeySpecifier (l, 0, _osCaption.HotKeySpecifier)).ToArray ()
+                    [_osCaption.Value!.Value];
+
+            // If Save style dialog then give them an overwrite prompt
+            if (_osCaption.Value == 2)
+            {
+                fd.FilesSelected += ConfirmOverwrite;
+            }
 
-        fd.Style.OkButtonText = _rgCaption.RadioLabels.Select (l => TextFormatter.RemoveHotKeySpecifier(l, 0, _rgCaption.HotKeySpecifier)).ToArray() [_rgCaption.SelectedItem];
+            fd.Style.IconProvider.UseUnicodeCharacters = _osIcons.Value == 1;
+            fd.Style.IconProvider.UseNerdIcons = _osIcons.Value == 2;
 
-        // If Save style dialog then give them an overwrite prompt
-        if (_rgCaption.SelectedItem == 2)
-        {
-            fd.FilesSelected += ConfirmOverwrite;
-        }
-
-        fd.Style.IconProvider.UseUnicodeCharacters = _rgIcons.SelectedItem == 1;
-        fd.Style.IconProvider.UseNerdIcons = _rgIcons.SelectedItem == 2;
+            if (_cbCaseSensitive.CheckedState == CheckState.Checked)
+            {
+                fd.SearchMatcher = new CaseSensitiveSearchMatcher ();
+            }
 
-        if (_cbCaseSensitive.CheckedState == CheckState.Checked)
-        {
-            fd.SearchMatcher = new CaseSensitiveSearchMatcher ();
-        }
+            fd.Style.UseColors = _cbUseColors.CheckedState == CheckState.Checked;
 
-        fd.Style.UseColors = _cbUseColors.CheckedState == CheckState.Checked;
+            fd.Style.TreeStyle.ShowBranchLines = _cbShowTreeBranchLines.CheckedState == CheckState.Checked;
+            fd.Style.TableStyle.AlwaysShowHeaders = _cbAlwaysTableShowHeaders.CheckedState == CheckState.Checked;
 
-        fd.Style.TreeStyle.ShowBranchLines = _cbShowTreeBranchLines.CheckedState == CheckState.Checked;
-        fd.Style.TableStyle.AlwaysShowHeaders = _cbAlwaysTableShowHeaders.CheckedState == CheckState.Checked;
+            IDirectoryInfoFactory dirInfoFactory = new FileSystem ().DirectoryInfo;
 
-        IDirectoryInfoFactory dirInfoFactory = new FileSystem ().DirectoryInfo;
+            if (_cbDrivesOnlyInTree.CheckedState == CheckState.Checked)
+            {
+                fd.Style.TreeRootGetter = () => { return Environment.GetLogicalDrives ().ToDictionary (dirInfoFactory.New, k => k); };
+            }
 
-        if (_cbDrivesOnlyInTree.CheckedState == CheckState.Checked)
-        {
-            fd.Style.TreeRootGetter = () => { return Environment.GetLogicalDrives ().ToDictionary (dirInfoFactory.New, k => k); };
-        }
+            fd.Style.PreserveFilenameOnDirectoryChanges = _cbPreserveFilenameOnDirectoryChanges.CheckedState == CheckState.Checked;
 
-        fd.Style.PreserveFilenameOnDirectoryChanges = _cbPreserveFilenameOnDirectoryChanges.CheckedState == CheckState.Checked;
-        
+            if (_osAllowedTypes.Value > 0)
+            {
+                fd.AllowedTypes.Add (new AllowedType ("Data File", ".csv", ".tsv"));
 
-        if (_rgAllowedTypes.SelectedItem > 0)
-        {
-            fd.AllowedTypes.Add (new AllowedType ("Data File", ".csv", ".tsv"));
+                if (_osAllowedTypes.Value == 1)
+                {
+                    fd.AllowedTypes.Insert (1, new AllowedTypeAny ());
+                }
+            }
 
-            if (_rgAllowedTypes.SelectedItem == 1)
+            if (!string.IsNullOrWhiteSpace (_tbOkButton.Text))
             {
-                fd.AllowedTypes.Insert (1, new AllowedTypeAny ());
+                fd.Style.OkButtonText = _tbOkButton.Text;
             }
-        }
 
-        if (!string.IsNullOrWhiteSpace (_tbOkButton.Text))
-        {
-            fd.Style.OkButtonText = _tbOkButton.Text;
-        }
-
-        if (!string.IsNullOrWhiteSpace (_tbCancelButton.Text))
-        {
-            fd.Style.CancelButtonText = _tbCancelButton.Text;
-        }
-
-        if (_cbFlipButtonOrder.CheckedState == CheckState.Checked)
-        {
-            fd.Style.FlipOkCancelButtonLayoutOrder = true;
-        }
+            if (!string.IsNullOrWhiteSpace (_tbCancelButton.Text))
+            {
+                fd.Style.CancelButtonText = _tbCancelButton.Text;
+            }
 
-        Application.Run (fd);
+            if (_cbFlipButtonOrder.CheckedState == CheckState.Checked)
+            {
+                fd.Style.FlipOkCancelButtonLayoutOrder = true;
+            }
 
-        var canceled = fd.Canceled;
-        var multiSelected = fd.MultiSelected;
-        var path = fd.Path;
+            Application.Run (fd);
 
-        // This needs to be disposed before opening other toplevel
-        fd.Dispose ();
+            bool canceled = fd.Canceled;
+            IReadOnlyList<string> multiSelected = fd.MultiSelected;
+            string path = fd.Path;
 
-        if (canceled)
-        {
-            MessageBox.Query (
-                              "Canceled",
-                              "You canceled navigation and did not pick anything",
-                              "Ok"
-                             );
+            // This needs to be disposed before opening other toplevel
+            fd.Dispose ();
 
-        }
-        else if (_cbAllowMultipleSelection.CheckedState == CheckState.Checked)
-        {
-            MessageBox.Query (
-                              "Chosen!",
-                              "You chose:" + Environment.NewLine + string.Join (Environment.NewLine, multiSelected.Select (m => m)),
-                              "Ok"
-                             );
-        }
-        else
-        {
-            MessageBox.Query (
-                              "Chosen!",
-                              "You chose:" + Environment.NewLine + path,
-                              "Ok"
-                             );
+            if (canceled)
+            {
+                MessageBox.Query (
+                                  "Canceled",
+                                  "You canceled navigation and did not pick anything",
+                                  "Ok"
+                                 );
+            }
+            else if (_cbAllowMultipleSelection.CheckedState == CheckState.Checked)
+            {
+                MessageBox.Query (
+                                  "Chosen!",
+                                  "You chose:" + Environment.NewLine + string.Join (Environment.NewLine, multiSelected.Select (m => m)),
+                                  "Ok"
+                                 );
+            }
+            else
+            {
+                MessageBox.Query (
+                                  "Chosen!",
+                                  "You chose:" + Environment.NewLine + path,
+                                  "Ok"
+                                 );
+            }
         }
     }
 

+ 16 - 16
Examples/UICatalog/Scenarios/Images.cs

@@ -50,8 +50,8 @@ public class Images : Scenario
     private SixelToRender _fireSixel;
     private int _fireFrameCounter;
     private bool _isDisposed;
-    private RadioGroup _rgPaletteBuilder;
-    private RadioGroup _rgDistanceAlgorithm;
+    private OptionSelector _osPaletteBuilder;
+    private OptionSelector _osDistanceAlgorithm;
     private NumericUpDown _popularityThreshold;
     private SixelToRender _sixelImage;
 
@@ -408,22 +408,22 @@ public class Images : Scenario
             Y = Pos.Bottom (_pxY) + 1
         };
 
-        _rgPaletteBuilder = new ()
+        _osPaletteBuilder = new ()
         {
-            RadioLabels = new []
-            {
+            Labels =
+            [
                 "Popularity",
                 "Median Cut"
-            },
+            ],
             X = Pos.Right (_sixelView) + 2,
             Y = Pos.Bottom (l1),
-            SelectedItem = 1
+            Value = 1
         };
 
         _popularityThreshold = new ()
         {
-            X = Pos.Right (_rgPaletteBuilder) + 1,
-            Y = Pos.Top (_rgPaletteBuilder),
+            X = Pos.Right (_osPaletteBuilder) + 1,
+            Y = Pos.Top (_osPaletteBuilder),
             Value = 8
         };
 
@@ -439,12 +439,12 @@ public class Images : Scenario
             Text = "Color Distance Algorithm",
             Width = Dim.Auto (),
             X = Pos.Right (_sixelView),
-            Y = Pos.Bottom (_rgPaletteBuilder) + 1
+            Y = Pos.Bottom (_osPaletteBuilder) + 1
         };
 
-        _rgDistanceAlgorithm = new ()
+        _osDistanceAlgorithm = new ()
         {
-            RadioLabels = new []
+            Labels = new []
             {
                 "Euclidian",
                 "CIE76"
@@ -458,10 +458,10 @@ public class Images : Scenario
         _sixelSupported.Add (lblPxY);
         _sixelSupported.Add (_pxY);
         _sixelSupported.Add (l1);
-        _sixelSupported.Add (_rgPaletteBuilder);
+        _sixelSupported.Add (_osPaletteBuilder);
 
         _sixelSupported.Add (l2);
-        _sixelSupported.Add (_rgDistanceAlgorithm);
+        _sixelSupported.Add (_osDistanceAlgorithm);
         _sixelSupported.Add (_popularityThreshold);
         _sixelSupported.Add (lblPopThreshold);
 
@@ -470,7 +470,7 @@ public class Images : Scenario
 
     private IPaletteBuilder GetPaletteBuilder ()
     {
-        switch (_rgPaletteBuilder.SelectedItem)
+        switch (_osPaletteBuilder.Value)
         {
             case 0: return new PopularityPaletteWithThreshold (GetDistanceAlgorithm (), _popularityThreshold.Value);
             case 1: return new MedianCutPaletteBuilder (GetDistanceAlgorithm ());
@@ -480,7 +480,7 @@ public class Images : Scenario
 
     private IColorDistance GetDistanceAlgorithm ()
     {
-        switch (_rgDistanceAlgorithm.SelectedItem)
+        switch (_osDistanceAlgorithm.Value)
         {
             case 0: return new EuclideanColorDistance ();
             case 1: return new CIE76ColorDistance ();

+ 10 - 4
Examples/UICatalog/Scenarios/LineDrawing.cs

@@ -210,7 +210,7 @@ public class ToolsView : Window
 {
     private Button _addLayerBtn;
     private readonly AttributeView _colors;
-    private RadioGroup _stylePicker;
+    private OptionSelector<LineStyle> _stylePicker;
 
     public Attribute CurrentColor
     {
@@ -236,10 +236,16 @@ public class ToolsView : Window
 
         _stylePicker = new ()
         {
-            X = 0, Y = Pos.Bottom (_colors), RadioLabels = Enum.GetNames (typeof (LineStyle)).ToArray ()
+            X = 0, Y = Pos.Bottom (_colors), AssignHotKeys = true
         };
-        _stylePicker.SelectedItemChanged += (s, a) => { SetStyle?.Invoke ((LineStyle)a.SelectedItem); };
-        _stylePicker.SelectedItem = 1;
+        _stylePicker.ValueChanged += (s, a) =>
+                                     {
+                                         if (a.Value is { })
+                                         {
+                                             SetStyle?.Invoke ((LineStyle)a.Value);
+                                         }
+                                     };
+        _stylePicker.Value = LineStyle.Single;
 
         _addLayerBtn = new () { Text = "New Layer", X = Pos.Center (), Y = Pos.Bottom (_stylePicker) };
 

+ 9 - 8
Examples/UICatalog/Scenarios/MessageBoxes.cs

@@ -182,18 +182,19 @@ public class MessageBoxes : Scenario
         };
         frame.Add (label);
 
-        var styleRadioGroup = new RadioGroup
+        var styleOptionSelector = new OptionSelector ()
         {
-            X = Pos.Right (label) + 1, 
-            Y = Pos.Top (label), 
-            RadioLabels = ["_Query", "_Error"],
+            X = Pos.Right (label) + 1,
+            Y = Pos.Top (label),
+            Labels = ["_Query", "_Error"],
+            Title = "Sty_le"
         };
-        frame.Add (styleRadioGroup);
+        frame.Add (styleOptionSelector);
 
         label = new ()
         {
             X = 0,
-            Y = Pos.Bottom (styleRadioGroup),
+            Y = Pos.Bottom (styleOptionSelector),
 
             Width = Dim.Width (label),
             Height = 1,
@@ -202,7 +203,7 @@ public class MessageBoxes : Scenario
         };
         var ckbWrapMessage = new CheckBox
         {
-            X = Pos.Right (label) + 1, Y = Pos.Bottom (styleRadioGroup),
+            X = Pos.Right (label) + 1, Y = Pos.Bottom (styleOptionSelector),
             CheckedState = CheckState.Checked,
             Text = "_Wrap Message",
         };
@@ -246,7 +247,7 @@ public class MessageBoxes : Scenario
                                                    btns.Add ($"_{NumberToWords.Convert (i)}");
                                                }
 
-                                               if (styleRadioGroup.SelectedItem == 0)
+                                               if (styleOptionSelector.Value == 0)
                                                {
                                                    buttonPressedLabel.Text =
                                                        $"{MessageBox.Query (

+ 141 - 135
Examples/UICatalog/Scenarios/PosAlignDemo.cs

@@ -1,14 +1,10 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
 namespace UICatalog.Scenarios;
 
 [ScenarioMetadata ("Pos.Align", "Demonstrates Pos.Align")]
 [ScenarioCategory ("Layout")]
 public sealed class PosAlignDemo : Scenario
 {
-    private readonly Aligner _horizAligner = new () { AlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems};
+    private readonly Aligner _horizAligner = new () { AlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems };
     private int _leftMargin;
     private readonly Aligner _vertAligner = new () { AlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems };
     private int _topMargin;
@@ -40,43 +36,42 @@ public sealed class PosAlignDemo : Scenario
 
     private void SetupControls (Window appWindow, Dimension dimension, Schemes scheme)
     {
-        RadioGroup alignRadioGroup = new ()
+        OptionSelector<Alignment> alignOptionSelector = new ()
         {
-            RadioLabels = Enum.GetNames<Alignment> (),
-            SchemeName = SchemeManager.SchemesToSchemeName (scheme),
+            SchemeName = SchemeManager.SchemesToSchemeName (scheme)
         };
 
         if (dimension == Dimension.Width)
         {
-            alignRadioGroup.X = Pos.Align (_horizAligner.Alignment);
-            alignRadioGroup.Y = Pos.Center ();
+            alignOptionSelector.X = Pos.Align (_horizAligner.Alignment);
+            alignOptionSelector.Y = Pos.Center ();
         }
         else
         {
-            alignRadioGroup.X = Pos.Center ();
-            alignRadioGroup.Y = Pos.Align (_vertAligner.Alignment);
+            alignOptionSelector.X = Pos.Center ();
+            alignOptionSelector.Y = Pos.Align (_vertAligner.Alignment);
         }
 
-        alignRadioGroup.SelectedItemChanged += (s, e) =>
-                                               {
-                                                   if (dimension == Dimension.Width)
-                                                   {
-                                                       _horizAligner.Alignment =
-                                                           (Alignment)Enum.Parse (
-                                                                                  typeof (Alignment),
-                                                                                  alignRadioGroup.RadioLabels [alignRadioGroup.SelectedItem]);
-                                                       UpdatePosAlignObjects (appWindow, dimension, _horizAligner);
-                                                   }
-                                                   else
-                                                   {
-                                                       _vertAligner.Alignment =
-                                                           (Alignment)Enum.Parse (
-                                                                                  typeof (Alignment),
-                                                                                  alignRadioGroup.RadioLabels [alignRadioGroup.SelectedItem]);
-                                                       UpdatePosAlignObjects (appWindow, dimension, _vertAligner);
-                                                   }
-                                               };
-        appWindow.Add (alignRadioGroup);
+        alignOptionSelector.ValueChanged += (s, e) =>
+                                            {
+                                                if (alignOptionSelector.Value is null)
+                                                {
+                                                    return;
+                                                }
+
+                                                if (dimension == Dimension.Width)
+                                                {
+                                                    _horizAligner.Alignment = alignOptionSelector.Value.Value;
+
+                                                    UpdatePosAlignObjects (appWindow, dimension, _horizAligner);
+                                                }
+                                                else
+                                                {
+                                                    _vertAligner.Alignment = alignOptionSelector.Value.Value;
+                                                    UpdatePosAlignObjects (appWindow, dimension, _vertAligner);
+                                                }
+                                            };
+        appWindow.Add (alignOptionSelector);
 
         CheckBox endToStartCheckBox = new ()
         {
@@ -88,32 +83,32 @@ public sealed class PosAlignDemo : Scenario
         {
             endToStartCheckBox.CheckedState = _horizAligner.AlignmentModes.HasFlag (AlignmentModes.EndToStart) ? CheckState.Checked : CheckState.UnChecked;
             endToStartCheckBox.X = Pos.Align (_horizAligner.Alignment);
-            endToStartCheckBox.Y = Pos.Top (alignRadioGroup);
+            endToStartCheckBox.Y = Pos.Top (alignOptionSelector);
         }
         else
         {
             endToStartCheckBox.CheckedState = _vertAligner.AlignmentModes.HasFlag (AlignmentModes.EndToStart) ? CheckState.Checked : CheckState.UnChecked;
-            endToStartCheckBox.X = Pos.Left (alignRadioGroup);
+            endToStartCheckBox.X = Pos.Left (alignOptionSelector);
             endToStartCheckBox.Y = Pos.Align (_vertAligner.Alignment);
         }
 
         endToStartCheckBox.CheckedStateChanging += (s, e) =>
-                                      {
-                                          if (dimension == Dimension.Width)
-                                          {
-                                              _horizAligner.AlignmentModes = e.Result == CheckState.Checked
-                                                                                 ? _horizAligner.AlignmentModes | AlignmentModes.EndToStart
-                                                                                 : _horizAligner.AlignmentModes & ~AlignmentModes.EndToStart;
-                                              UpdatePosAlignObjects (appWindow, dimension, _horizAligner);
-                                          }
-                                          else
-                                          {
-                                              _vertAligner.AlignmentModes = e.Result == CheckState.Checked
-                                                                                ? _vertAligner.AlignmentModes | AlignmentModes.EndToStart
-                                                                                : _vertAligner.AlignmentModes & ~AlignmentModes.EndToStart;
-                                              UpdatePosAlignObjects (appWindow, dimension, _vertAligner);
-                                          }
-                                      };
+                                                   {
+                                                       if (dimension == Dimension.Width)
+                                                       {
+                                                           _horizAligner.AlignmentModes = e.Result == CheckState.Checked
+                                                                                              ? _horizAligner.AlignmentModes | AlignmentModes.EndToStart
+                                                                                              : _horizAligner.AlignmentModes & ~AlignmentModes.EndToStart;
+                                                           UpdatePosAlignObjects (appWindow, dimension, _horizAligner);
+                                                       }
+                                                       else
+                                                       {
+                                                           _vertAligner.AlignmentModes = e.Result == CheckState.Checked
+                                                                                             ? _vertAligner.AlignmentModes | AlignmentModes.EndToStart
+                                                                                             : _vertAligner.AlignmentModes & ~AlignmentModes.EndToStart;
+                                                           UpdatePosAlignObjects (appWindow, dimension, _vertAligner);
+                                                       }
+                                                   };
         appWindow.Add (endToStartCheckBox);
 
         CheckBox ignoreFirstOrLast = new ()
@@ -124,34 +119,35 @@ public sealed class PosAlignDemo : Scenario
 
         if (dimension == Dimension.Width)
         {
-            ignoreFirstOrLast.CheckedState = _horizAligner.AlignmentModes.HasFlag (AlignmentModes.IgnoreFirstOrLast) ? CheckState.Checked : CheckState.UnChecked;
+            ignoreFirstOrLast.CheckedState =
+                _horizAligner.AlignmentModes.HasFlag (AlignmentModes.IgnoreFirstOrLast) ? CheckState.Checked : CheckState.UnChecked;
             ignoreFirstOrLast.X = Pos.Align (_horizAligner.Alignment);
-            ignoreFirstOrLast.Y = Pos.Top (alignRadioGroup);
+            ignoreFirstOrLast.Y = Pos.Top (alignOptionSelector);
         }
         else
         {
             ignoreFirstOrLast.CheckedState = _vertAligner.AlignmentModes.HasFlag (AlignmentModes.IgnoreFirstOrLast) ? CheckState.Checked : CheckState.UnChecked;
-            ignoreFirstOrLast.X = Pos.Left (alignRadioGroup);
+            ignoreFirstOrLast.X = Pos.Left (alignOptionSelector);
             ignoreFirstOrLast.Y = Pos.Align (_vertAligner.Alignment);
         }
 
         ignoreFirstOrLast.CheckedStateChanging += (s, e) =>
-                                     {
-                                         if (dimension == Dimension.Width)
-                                         {
-                                             _horizAligner.AlignmentModes = e.Result == CheckState.Checked
-                                                     ? _horizAligner.AlignmentModes | AlignmentModes.IgnoreFirstOrLast
-                                                     : _horizAligner.AlignmentModes & ~AlignmentModes.IgnoreFirstOrLast;
-                                             UpdatePosAlignObjects (appWindow, dimension, _horizAligner);
-                                         }
-                                         else
-                                         {
-                                             _vertAligner.AlignmentModes = e.Result == CheckState.Checked
-                                                                               ? _vertAligner.AlignmentModes | AlignmentModes.IgnoreFirstOrLast
-                                                                               : _vertAligner.AlignmentModes & ~AlignmentModes.IgnoreFirstOrLast;
-                                             UpdatePosAlignObjects (appWindow, dimension, _vertAligner);
-                                         }
-                                     };
+                                                  {
+                                                      if (dimension == Dimension.Width)
+                                                      {
+                                                          _horizAligner.AlignmentModes = e.Result == CheckState.Checked
+                                                                                             ? _horizAligner.AlignmentModes | AlignmentModes.IgnoreFirstOrLast
+                                                                                             : _horizAligner.AlignmentModes & ~AlignmentModes.IgnoreFirstOrLast;
+                                                          UpdatePosAlignObjects (appWindow, dimension, _horizAligner);
+                                                      }
+                                                      else
+                                                      {
+                                                          _vertAligner.AlignmentModes = e.Result == CheckState.Checked
+                                                                                            ? _vertAligner.AlignmentModes | AlignmentModes.IgnoreFirstOrLast
+                                                                                            : _vertAligner.AlignmentModes & ~AlignmentModes.IgnoreFirstOrLast;
+                                                          UpdatePosAlignObjects (appWindow, dimension, _vertAligner);
+                                                      }
+                                                  };
         appWindow.Add (ignoreFirstOrLast);
 
         CheckBox addSpacesBetweenItems = new ()
@@ -162,34 +158,40 @@ public sealed class PosAlignDemo : Scenario
 
         if (dimension == Dimension.Width)
         {
-            addSpacesBetweenItems.CheckedState = _horizAligner.AlignmentModes.HasFlag (AlignmentModes.AddSpaceBetweenItems) ? CheckState.Checked : CheckState.UnChecked;
+            addSpacesBetweenItems.CheckedState =
+                _horizAligner.AlignmentModes.HasFlag (AlignmentModes.AddSpaceBetweenItems) ? CheckState.Checked : CheckState.UnChecked;
             addSpacesBetweenItems.X = Pos.Align (_horizAligner.Alignment);
-            addSpacesBetweenItems.Y = Pos.Top (alignRadioGroup);
+            addSpacesBetweenItems.Y = Pos.Top (alignOptionSelector);
         }
         else
         {
-            addSpacesBetweenItems.CheckedState = _vertAligner.AlignmentModes.HasFlag (AlignmentModes.AddSpaceBetweenItems) ? CheckState.Checked : CheckState.UnChecked;
-            addSpacesBetweenItems.X = Pos.Left (alignRadioGroup);
+            addSpacesBetweenItems.CheckedState =
+                _vertAligner.AlignmentModes.HasFlag (AlignmentModes.AddSpaceBetweenItems) ? CheckState.Checked : CheckState.UnChecked;
+            addSpacesBetweenItems.X = Pos.Left (alignOptionSelector);
             addSpacesBetweenItems.Y = Pos.Align (_vertAligner.Alignment);
         }
 
         addSpacesBetweenItems.CheckedStateChanging += (s, e) =>
-                                         {
-                                             if (dimension == Dimension.Width)
-                                             {
-                                                 _horizAligner.AlignmentModes = e.Result == CheckState.Checked
-                                                                                    ? _horizAligner.AlignmentModes | AlignmentModes.AddSpaceBetweenItems
-                                                                                    : _horizAligner.AlignmentModes & ~AlignmentModes.AddSpaceBetweenItems;
-                                                 UpdatePosAlignObjects (appWindow, dimension, _horizAligner);
-                                             }
-                                             else
-                                             {
-                                                 _vertAligner.AlignmentModes = e.Result == CheckState.Checked
-                                                                                   ? _vertAligner.AlignmentModes | AlignmentModes.AddSpaceBetweenItems
-                                                                                   : _vertAligner.AlignmentModes & ~AlignmentModes.AddSpaceBetweenItems;
-                                                 UpdatePosAlignObjects (appWindow, dimension, _vertAligner);
-                                             }
-                                         };
+                                                      {
+                                                          if (dimension == Dimension.Width)
+                                                          {
+                                                              _horizAligner.AlignmentModes = e.Result == CheckState.Checked
+                                                                                                 ? _horizAligner.AlignmentModes
+                                                                                                   | AlignmentModes.AddSpaceBetweenItems
+                                                                                                 : _horizAligner.AlignmentModes
+                                                                                                   & ~AlignmentModes.AddSpaceBetweenItems;
+                                                              UpdatePosAlignObjects (appWindow, dimension, _horizAligner);
+                                                          }
+                                                          else
+                                                          {
+                                                              _vertAligner.AlignmentModes = e.Result == CheckState.Checked
+                                                                                                ? _vertAligner.AlignmentModes
+                                                                                                  | AlignmentModes.AddSpaceBetweenItems
+                                                                                                : _vertAligner.AlignmentModes
+                                                                                                  & ~AlignmentModes.AddSpaceBetweenItems;
+                                                              UpdatePosAlignObjects (appWindow, dimension, _vertAligner);
+                                                          }
+                                                      };
 
         appWindow.Add (addSpacesBetweenItems);
 
@@ -202,7 +204,7 @@ public sealed class PosAlignDemo : Scenario
         if (dimension == Dimension.Width)
         {
             margin.X = Pos.Align (_horizAligner.Alignment);
-            margin.Y = Pos.Top (alignRadioGroup);
+            margin.Y = Pos.Top (alignOptionSelector);
         }
         else
         {
@@ -211,31 +213,31 @@ public sealed class PosAlignDemo : Scenario
         }
 
         margin.CheckedStateChanging += (s, e) =>
-                          {
-                              if (dimension == Dimension.Width)
-                              {
-                                  _leftMargin = e.Result == CheckState.Checked ? 1 : 0;
-                                  UpdatePosAlignObjects (appWindow, dimension, _horizAligner);
-                              }
-                              else
-                              {
-                                  _topMargin = e.Result == CheckState.Checked ? 1 : 0;
-                                  UpdatePosAlignObjects (appWindow, dimension, _vertAligner);
-                              }
-                          };
+                                       {
+                                           if (dimension == Dimension.Width)
+                                           {
+                                               _leftMargin = e.Result == CheckState.Checked ? 1 : 0;
+                                               UpdatePosAlignObjects (appWindow, dimension, _horizAligner);
+                                           }
+                                           else
+                                           {
+                                               _topMargin = e.Result == CheckState.Checked ? 1 : 0;
+                                               UpdatePosAlignObjects (appWindow, dimension, _vertAligner);
+                                           }
+                                       };
         appWindow.Add (margin);
 
         List<Button> addedViews =
         [
             new ()
             {
-                X = dimension == Dimension.Width ? Pos.Align (_horizAligner.Alignment) : Pos.Left (alignRadioGroup),
-                Y = dimension == Dimension.Width ? Pos.Top (alignRadioGroup) : Pos.Align (_vertAligner.Alignment),
+                X = dimension == Dimension.Width ? Pos.Align (_horizAligner.Alignment) : Pos.Left (alignOptionSelector),
+                Y = dimension == Dimension.Width ? Pos.Top (alignOptionSelector) : Pos.Align (_vertAligner.Alignment),
                 Text = NumberToWords.Convert (0)
             }
         ];
 
-        NumericUpDown<int> addedViewsUpDown = new()
+        NumericUpDown<int> addedViewsUpDown = new ()
         {
             Width = 9,
             Title = "Added",
@@ -247,12 +249,12 @@ public sealed class PosAlignDemo : Scenario
         if (dimension == Dimension.Width)
         {
             addedViewsUpDown.X = Pos.Align (_horizAligner.Alignment);
-            addedViewsUpDown.Y = Pos.Top (alignRadioGroup);
+            addedViewsUpDown.Y = Pos.Top (alignOptionSelector);
             addedViewsUpDown.Border!.Thickness = new (0, 1, 0, 0);
         }
         else
         {
-            addedViewsUpDown.X = Pos.Left (alignRadioGroup);
+            addedViewsUpDown.X = Pos.Left (alignOptionSelector);
             addedViewsUpDown.Y = Pos.Align (_vertAligner.Alignment);
             addedViewsUpDown.Border!.Thickness = new (1, 0, 0, 0);
         }
@@ -286,8 +288,10 @@ public sealed class PosAlignDemo : Scenario
                                                   {
                                                       var button = new Button
                                                       {
-                                                          X = dimension == Dimension.Width ? Pos.Align (_horizAligner.Alignment) : Pos.Left (alignRadioGroup),
-                                                          Y = dimension == Dimension.Width ? Pos.Top (alignRadioGroup) : Pos.Align (_vertAligner.Alignment),
+                                                          X = dimension == Dimension.Width
+                                                                  ? Pos.Align (_horizAligner.Alignment)
+                                                                  : Pos.Left (alignOptionSelector),
+                                                          Y = dimension == Dimension.Width ? Pos.Top (alignOptionSelector) : Pos.Align (_vertAligner.Alignment),
                                                           Text = NumberToWords.Convert (i + 1)
                                                       };
                                                       appWindow.Add (button);
@@ -351,46 +355,48 @@ public sealed class PosAlignDemo : Scenario
             Width = Dim.Percent (40),
             Height = Dim.Percent (40)
         };
-        container.Padding.Thickness = new (8, 1, 0, 0);
+        container.Padding!.Thickness = new (8, 1, 0, 0);
         container.Padding.SchemeName = "Error";
 
         Aligner widthAligner = new () { AlignmentModes = AlignmentModes.StartToEnd };
 
-        RadioGroup widthAlignRadioGroup = new ()
+        OptionSelector widthAlignOptionSelector = new ()
         {
-            RadioLabels = Enum.GetNames<Alignment> (),
+            Labels = Enum.GetNames<Alignment> (),
             Orientation = Orientation.Horizontal,
             X = Pos.Center ()
         };
-        container.Padding.Add (widthAlignRadioGroup);
-
-        widthAlignRadioGroup.SelectedItemChanged += (sender, e) =>
-                                                    {
-                                                        widthAligner.Alignment =
-                                                            (Alignment)Enum.Parse (
-                                                                                   typeof (Alignment),
-                                                                                   widthAlignRadioGroup.RadioLabels [widthAlignRadioGroup.SelectedItem]);
-                                                        UpdatePosAlignObjects (container, Dimension.Width, widthAligner);
-                                                    };
+        container.Padding.Add (widthAlignOptionSelector);
+
+        widthAlignOptionSelector.ValueChanged += (_, _) =>
+                                                        {
+                                                            widthAligner.Alignment =
+                                                                (Alignment)Enum.Parse (
+                                                                                       typeof (Alignment),
+                                                                                       widthAlignOptionSelector.Labels [widthAlignOptionSelector
+                                                                                           .Value!.Value]);
+                                                            UpdatePosAlignObjects (container, Dimension.Width, widthAligner);
+                                                        };
 
         Aligner heightAligner = new () { AlignmentModes = AlignmentModes.StartToEnd };
 
-        RadioGroup heightAlignRadioGroup = new ()
+        OptionSelector heightAlignOptionSelector = new ()
         {
-            RadioLabels = Enum.GetNames<Alignment> (),
+            Labels = Enum.GetNames<Alignment> (),
             Orientation = Orientation.Vertical,
             Y = Pos.Center ()
         };
-        container.Padding.Add (heightAlignRadioGroup);
-
-        heightAlignRadioGroup.SelectedItemChanged += (sender, e) =>
-                                                     {
-                                                         heightAligner.Alignment =
-                                                             (Alignment)Enum.Parse (
-                                                                                    typeof (Alignment),
-                                                                                    heightAlignRadioGroup.RadioLabels [heightAlignRadioGroup.SelectedItem]);
-                                                         UpdatePosAlignObjects (container, Dimension.Height, heightAligner);
-                                                     };
+        container.Padding.Add (heightAlignOptionSelector);
+
+        heightAlignOptionSelector.ValueChanged += (sender, e) =>
+                                                         {
+                                                             heightAligner.Alignment =
+                                                                 (Alignment)Enum.Parse (
+                                                                                        typeof (Alignment),
+                                                                                        heightAlignOptionSelector.Labels [heightAlignOptionSelector
+                                                                                            .Value!.Value]);
+                                                             UpdatePosAlignObjects (container, Dimension.Height, heightAligner);
+                                                         };
 
         for (var i = 0; i < 9; i++)
 
@@ -401,7 +407,7 @@ public sealed class PosAlignDemo : Scenario
                 Text = $"{i}",
                 BorderStyle = LineStyle.Dashed,
                 Height = Dim.Auto (),
-                Width = Dim.Auto() + 2
+                Width = Dim.Auto () + 2
             };
 
             v.X = Pos.Align (widthAligner.Alignment, widthAligner.AlignmentModes, i / 3);

+ 17 - 11
Examples/UICatalog/Scenarios/ProgressBarStyles.cs

@@ -132,15 +132,15 @@ public class ProgressBarStyles : Scenario
         List<ProgressBarFormat> pbFormatEnum =
             Enum.GetValues (typeof (ProgressBarFormat)).Cast<ProgressBarFormat> ().ToList ();
 
-        var rbPBFormat = new RadioGroup
+        OptionSelector<ProgressBarFormat> osPbFormat = new ()
         {
             BorderStyle = LineStyle.Single,
             Title = "ProgressBarFormat",
             X = Pos.Center (),
             Y = Pos.Align (Alignment.Start),
-            RadioLabels = pbFormatEnum.Select (e => e.ToString ()).ToArray ()
+            AssignHotKeys = true
         };
-        container.Add (rbPBFormat);
+        container.Add (osPbFormat);
 
         var button = new Button
         {
@@ -161,7 +161,7 @@ public class ProgressBarStyles : Scenario
         };
         container.Add (blocksPB);
 
-        rbPBFormat.SelectedItem = (int)blocksPB.ProgressBarFormat;
+        osPbFormat.Value = blocksPB.ProgressBarFormat;
 
         var continuousPB = new ProgressBar
         {
@@ -256,13 +256,19 @@ public class ProgressBarStyles : Scenario
                                       };
 
 
-        rbPBFormat.SelectedItemChanged += (s, e) =>
-                                          {
-                                              blocksPB.ProgressBarFormat = (ProgressBarFormat)e.SelectedItem;
-                                              continuousPB.ProgressBarFormat = (ProgressBarFormat)e.SelectedItem;
-                                              marqueesBlocksPB.ProgressBarFormat = (ProgressBarFormat)e.SelectedItem;
-                                              marqueesContinuousPB.ProgressBarFormat = (ProgressBarFormat)e.SelectedItem;
-                                          };
+        osPbFormat.ValueChanged += (s, e) =>
+                                   {
+                                       if (e.Value is null)
+                                       {
+                                           return;
+                                       }
+
+                                       blocksPB.ProgressBarFormat = e.Value.Value;
+                                       continuousPB.ProgressBarFormat = e.Value.Value;
+                                       marqueesBlocksPB.ProgressBarFormat = e.Value.Value;
+                                       marqueesContinuousPB.ProgressBarFormat = e.Value.Value;
+
+                                   };
 
         ckbBidirectional.CheckedStateChanging += (s, e) =>
                                    {

+ 18 - 45
Examples/UICatalog/Scenarios/RegionScenario.cs

@@ -1,7 +1,4 @@
 #nullable enable
-using System;
-using System.Collections.Generic;
-using System.Linq;
 using System.Text;
 using UICatalog;
 using UICatalog.Scenarios;
@@ -40,7 +37,7 @@ public class RegionScenario : Scenario
 
         tools.SetStyle += b =>
                           {
-                              _drawStyle = (RegionDrawStyles)b;
+                              _drawStyle = b;
                               app.SetNeedsDraw ();
                           };
 
@@ -169,8 +166,8 @@ public class ToolsView : Window
 {
     //private Button _addLayerBtn;
     private readonly AttributeView _attributeView = new ();
-    private RadioGroup? _stylePicker;
-    private RegionOpSelector? _regionOpSelector;
+    private OptionSelector<RegionDrawStyles>? _stylePicker;
+    private OptionSelector<RegionOp>? _regionOpSelector;
 
     public Attribute? CurrentAttribute
     {
@@ -197,22 +194,30 @@ public class ToolsView : Window
         _stylePicker = new ()
         {
             Width = Dim.Fill (),
-            X = 0, Y = Pos.Bottom (_attributeView) + 1, RadioLabels = Enum.GetNames<RegionDrawStyles> ().Select (n => n = "_" + n).ToArray ()
+            X = 0, Y = Pos.Bottom (_attributeView) + 1,
+            AssignHotKeys = true
         };
         _stylePicker.BorderStyle = LineStyle.Single;
         _stylePicker.Border!.Thickness = new (0, 1, 0, 0);
         _stylePicker.Title = "Draw Style";
 
-        _stylePicker.SelectedItemChanged += (s, a) => { SetStyle?.Invoke ((LineStyle)a.SelectedItem!); };
-        _stylePicker.SelectedItem = (int)RegionDrawStyles.FillOnly;
+        _stylePicker.ValueChanged += (s, a) => { SetStyle?.Invoke ((RegionDrawStyles)a.Value!); };
+        _stylePicker.Value = RegionDrawStyles.FillOnly;
 
         _regionOpSelector = new ()
         {
             X = 0,
-            Y = Pos.Bottom (_stylePicker) + 1
+            Y = Pos.Bottom (_stylePicker) + 1,
+            AssignHotKeys = true
         };
-        _regionOpSelector.SelectedItemChanged += (s, a) => { RegionOpChanged?.Invoke (this, a); };
-        _regionOpSelector.SelectedItem = RegionOp.MinimalUnion;
+        _regionOpSelector.ValueChanged += (s, a) =>
+                                          {
+                                              if (a.Value is { })
+                                              {
+                                                  RegionOpChanged?.Invoke (this, (RegionOp)a.Value);
+                                              }
+                                          };
+        _regionOpSelector.Value = RegionOp.MinimalUnion;
 
         //_addLayerBtn = new () { Text = "New Layer", X = Pos.Center (), Y = Pos.Bottom (_stylePicker) };
 
@@ -222,39 +227,7 @@ public class ToolsView : Window
 
     public event EventHandler<Attribute?>? AttributeChanged;
     public event EventHandler<RegionOp>? RegionOpChanged;
-    public event Action<LineStyle>? SetStyle;
-}
-
-public class RegionOpSelector : View
-{
-    private readonly RadioGroup _radioGroup;
-
-    public RegionOpSelector ()
-    {
-        Width = Dim.Auto ();
-        Height = Dim.Auto ();
-
-        BorderStyle = LineStyle.Single;
-        Border!.Thickness = new (0, 1, 0, 0);
-        Title = "RegionOp";
-
-        _radioGroup = new ()
-        {
-            X = 0,
-            Y = 0,
-            RadioLabels = Enum.GetNames<RegionOp> ().Select (n => n = "_" + n).ToArray ()
-        };
-        _radioGroup.SelectedItemChanged += (s, a) => { SelectedItemChanged?.Invoke (this, (RegionOp)a.SelectedItem!); };
-        Add (_radioGroup);
-    }
-
-    public event EventHandler<RegionOp>? SelectedItemChanged;
-
-    public RegionOp SelectedItem
-    {
-        get => (RegionOp)_radioGroup.SelectedItem;
-        set => _radioGroup.SelectedItem = (int)value;
-    }
+    public event Action<RegionDrawStyles>? SetStyle;
 }
 
 public class AttributeView : View

+ 5 - 9
Examples/UICatalog/Scenarios/ScrollBarDemo.cs

@@ -118,23 +118,19 @@ public class ScrollBarDemo : Scenario
         };
         demoFrame.Add (lblOrientationLabel);
 
-        var rgOrientation = new RadioGroup
+        OptionSelector<Orientation> osOrientation = new ()
         {
             X = Pos.Right (lblOrientationLabel) + 1,
             Y = Pos.Top (lblOrientationLabel),
-            RadioLabels = ["Vertical", "Horizontal"],
+            AssignHotKeys = true,
             Orientation = Orientation.Horizontal
         };
-        demoFrame.Add (rgOrientation);
+        demoFrame.Add (osOrientation);
 
-        rgOrientation.SelectedItemChanged += (s, e) =>
+        osOrientation.ValueChanged += (s, e) =>
                                              {
-                                                 if (e.SelectedItem == e.PreviousSelectedItem)
-                                                 {
-                                                     return;
-                                                 }
 
-                                                 if (rgOrientation.SelectedItem == 0)
+                                                 if (osOrientation.Value == Orientation.Horizontal)
                                                  {
                                                      scrollBar.Orientation = Orientation.Vertical;
                                                      scrollBar.X = Pos.AnchorEnd () - 5;

+ 287 - 0
Examples/UICatalog/Scenarios/Selectors.cs

@@ -0,0 +1,287 @@
+#nullable enable
+
+namespace UICatalog.Scenarios;
+
+[ScenarioMetadata ("Selectors", "Demonstrates OptionSelector and FlagSelector.")]
+[ScenarioCategory ("Controls")]
+public sealed class Selectors : Scenario
+{
+    public override void Main ()
+    {
+        // Init
+        Application.Init ();
+
+        // Setup - Create a top-level application window and configure it.
+        Window appWindow = new ()
+        {
+            Title = GetQuitKeyAndName (),
+            BorderStyle = LineStyle.None
+        };
+
+        FrameView? optionSelectorsFrame = null;
+        FrameView? flagSelectorsFrame = null;
+
+        OptionSelector<Orientation> orientationSelector = new ()
+        {
+            Orientation = Orientation.Horizontal,
+            BorderStyle = LineStyle.Dotted,
+            Title = "Selector Or_ientation",
+            Value = Orientation.Vertical
+        };
+        orientationSelector.ValueChanged += OrientationSelectorOnSelectedItemChanged;
+
+        FlagSelector<SelectorStyles> stylesSelector = new ()
+        {
+            X = Pos.Right (orientationSelector) + 1,
+            Orientation = Orientation.Horizontal,
+            BorderStyle = LineStyle.Dotted,
+            Title = "Selector St_yles",
+        };
+        stylesSelector.ValueChanged += StylesSelectorOnValueChanged;
+
+        NumericUpDown<int> horizontalSpace = new ()
+        {
+            X = 0,
+            Y = Pos.Bottom (orientationSelector),
+            Width = 11,
+            Title = "H_. Space",
+            Value = stylesSelector.HorizontalSpace,
+            BorderStyle = LineStyle.Dotted,
+        };
+        horizontalSpace.ValueChanging += HorizontalSpaceOnValueChanging;
+
+        CheckBox showBorderAndTitle = new ()
+        {
+            X = Pos.Right (horizontalSpace) + 1,
+            Y = Pos.Top (horizontalSpace),
+            Title = "Border _& Title",
+            CheckedState = CheckState.Checked,
+            BorderStyle = LineStyle.Dotted,
+        };
+        showBorderAndTitle.CheckedStateChanged += ShowBorderAndTitleOnCheckedStateChanged;
+
+        CheckBox canFocus = new ()
+        {
+            X = Pos.Right (showBorderAndTitle) + 1,
+            Y = Pos.Top (horizontalSpace),
+            Title = "_CanFocus",
+            CheckedState = CheckState.Checked,
+            BorderStyle = LineStyle.Dotted,
+        };
+        canFocus.CheckedStateChanged += CanFocusOnCheckedStateChanged;
+
+        optionSelectorsFrame = new ()
+        {
+            Y = Pos.Bottom (canFocus),
+            Width = Dim.Percent (50),
+            Height = Dim.Fill (),
+            Title = "O_ptionSelectors",
+            TabStop = TabBehavior.TabStop,
+            //InvertFocusAttribute = true
+        };
+        optionSelectorsFrame.ClearingViewport += (sender, args) =>
+                                                 {
+                                                     //               optionSelectorsFrame.SetAttributeForRole (optionSelectorsFrame.HasFocus ? VisualRole.Focus : VisualRole.Normal);
+                                                 };
+
+
+        Label label = new ()
+        {
+            Title = "Fo_ur Options:"
+        };
+
+        OptionSelector optionSelector = new ()
+        {
+            X = Pos.Right (label) + 1,
+            Title = "Fou_r Options",
+            BorderStyle = LineStyle.Dotted,
+            UsedHotKeys = { label.HotKey },
+            AssignHotKeys = true,
+            Labels = ["Option _1 (0)", "Option _2 (1)", "Option _3 (5) 你", "Option _Quattro (4) 你"],
+            Values = [0, 1, 5, 4],
+            Arrangement = ViewArrangement.Resizable,
+        };
+        optionSelectorsFrame.Add (label, optionSelector);
+
+        label = new ()
+        {
+            Y = Pos.Bottom (optionSelector),
+            Title = "<VisualRole_>:"
+        };
+
+        OptionSelector<VisualRole> optionSelectorT = new ()
+        {
+            X = Pos.Right (label) + 1,
+            Y = Pos.Bottom (optionSelector),
+            Title = "<Vi_sualRole>",
+            BorderStyle = LineStyle.Dotted,
+            UsedHotKeys = optionSelector.UsedHotKeys,
+            AssignHotKeys = true,
+        };
+
+        optionSelectorsFrame.Add (label, optionSelectorT);
+
+        flagSelectorsFrame = new ()
+        {
+            Y = Pos.Top (optionSelectorsFrame),
+            X = Pos.Right (optionSelectorsFrame),
+            Width = Dim.Fill (),
+            Height = Dim.Fill (),
+            Title = "_FlagSelectors",
+            TabStop = TabBehavior.TabStop
+        };
+
+        label = new ()
+        {
+            Title = "FlagSelector _(uint):"
+        };
+
+        FlagSelector flagSelector = new ()
+        {
+            X = Pos.Right (label) + 1,
+            UsedHotKeys = optionSelectorT.UsedHotKeys,
+            BorderStyle = LineStyle.Dotted,
+            Title = "FlagSe_lector (uint)",
+            AssignHotKeys = true,
+            Values =
+            [
+                0b_0001,
+                0b_0010,
+                0b_0100,
+                0b_1000,
+                0b_1111
+            ],
+            Labels =
+            [
+                "0x0001 One",
+                "0x0010 Two",
+                "0x0100 Quattro",
+                "0x1000 8",
+                "0x1111 Fifteen"
+            ]
+        };
+        flagSelectorsFrame.Add (label, flagSelector);
+
+        label = new ()
+        {
+            Y = Pos.Bottom (flagSelector),
+            Title = "_<ViewDiagnosticFlags>:"
+        };
+
+        FlagSelector<ViewDiagnosticFlags> flagSelectorT = new ()
+        {
+            X = Pos.Right (label) + 1,
+            BorderStyle = LineStyle.Dotted,
+            Title = "<ViewD_iagnosticFlags>",
+            Y = Pos.Bottom (flagSelector),
+            UsedHotKeys = flagSelector.UsedHotKeys,
+            AssignHotKeys = true,
+            Value = View.Diagnostics
+        };
+        flagSelectorsFrame.Add (label, flagSelectorT);
+        flagSelectorT.ValueChanged += (s, a) =>
+                                      {
+                                          View.Diagnostics = (ViewDiagnosticFlags)a.Value!;
+                                      };
+
+        appWindow.Add (orientationSelector, stylesSelector, horizontalSpace, showBorderAndTitle, canFocus, optionSelectorsFrame, flagSelectorsFrame);
+
+        // Run - Start the application.
+        Application.Run (appWindow);
+        appWindow.Dispose ();
+
+        // Shutdown - Calling Application.Shutdown is required.
+        Application.Shutdown ();
+
+        return;
+
+        void OrientationSelectorOnSelectedItemChanged (object? sender, EventArgs<Orientation?> e)
+        {
+            if (sender is not OptionSelector<Orientation> s)
+            {
+                return;
+            }
+
+            List<SelectorBase> selectors = GetAllSelectors ();
+            foreach (SelectorBase selector in selectors)
+            {
+                selector.Orientation = s.Value!.Value;
+            }
+        }
+
+        void StylesSelectorOnValueChanged (object? sender, EventArgs<SelectorStyles?> e)
+        {
+            if (sender is not FlagSelector<SelectorStyles> s)
+            {
+                return;
+            }
+
+            List<SelectorBase> selectors = GetAllSelectors ();
+
+            foreach (SelectorBase selector in selectors)
+            {
+                selector.Styles = s.Value!.Value;
+            }
+        }
+
+        void HorizontalSpaceOnValueChanging (object? sender, CancelEventArgs<int> e)
+        {
+            if (sender is not NumericUpDown<int> upDown || e.NewValue < 0)
+            {
+                e.Cancel = true;
+                return;
+            }
+
+            List<SelectorBase> selectors = GetAllSelectors ();
+
+            foreach (SelectorBase selector in selectors)
+            {
+                selector.HorizontalSpace = e.NewValue;
+            }
+        }
+
+
+        void ShowBorderAndTitleOnCheckedStateChanged (object? sender, EventArgs<CheckState> e)
+        {
+            if (sender is not CheckBox cb)
+            {
+                return;
+            }
+
+            List<SelectorBase> selectors = GetAllSelectors ();
+
+            foreach (SelectorBase selector in selectors)
+            {
+                selector.Border!.Thickness = cb.CheckedState == CheckState.Checked ? new (1) : new Thickness (0);
+            }
+        }
+
+        void CanFocusOnCheckedStateChanged (object? sender, EventArgs<CheckState> e)
+        {
+            if (sender is not CheckBox cb)
+            {
+                return;
+            }
+
+            List<SelectorBase> selectors = GetAllSelectors ();
+
+            foreach (SelectorBase selector in selectors)
+            {
+                selector.CanFocus = cb.CheckedState == CheckState.Checked;
+            }
+        }
+
+        List<SelectorBase> GetAllSelectors ()
+        {
+            List<SelectorBase> optionSelectors = [];
+            // ReSharper disable once AccessToModifiedClosure
+            optionSelectors.AddRange (optionSelectorsFrame!.SubViews.OfType<SelectorBase> ());
+            // ReSharper disable once AccessToModifiedClosure
+            optionSelectors.AddRange (flagSelectorsFrame!.SubViews.OfType<FlagSelector> ());
+
+            return optionSelectors;
+        }
+    }
+
+
+}

+ 102 - 65
Examples/UICatalog/Scenarios/Shortcuts.cs

@@ -66,47 +66,26 @@ public class Shortcuts : Scenario
             {
                 Text = "_Align Keys",
                 CanFocus = false,
-                HighlightStates = MouseState.None
+                HighlightStates = MouseState.None,
+                CheckedState = CheckState.Checked
             },
             Key = Key.F5.WithCtrl.WithAlt.WithShift
         };
 
-        // ((CheckBox)vShortcut3.CommandView).CheckedStateChanging += (_, args) =>
         ((CheckBox)alignKeysShortcut.CommandView).CheckedStateChanging += (s, e) =>
                                                                           {
                                                                               if (alignKeysShortcut.CommandView is CheckBox cb)
                                                                               {
+                                                                                  bool align = e.Result == CheckState.Checked;
                                                                                   eventSource.Add (
                                                                                                    $"{alignKeysShortcut.Id}.CommandView.CheckedStateChanging: {cb.Text}");
                                                                                   eventLog.MoveDown ();
 
-                                                                                  var max = 0;
-
-                                                                                  IEnumerable<View> toAlign =
-                                                                                      Application.Top.SubViews.Where (
-                                                                                       v => v is Shortcut { Width: not DimAbsolute });
-                                                                                  IEnumerable<View> enumerable = toAlign as View [] ?? toAlign.ToArray ();
-
-                                                                                  if (e.Result == CheckState.Checked)
-                                                                                  {
-                                                                                      max = (from Shortcut? peer in enumerable
-                                                                                             select peer.Key.ToString ().GetColumns ()).Prepend (max)
-                                                                                          .Max ();
-
-                                                                                      foreach (View view in enumerable)
-                                                                                      {
-                                                                                          var peer = (Shortcut)view;
-                                                                                          max = Math.Max (max, peer.KeyView.Text.GetColumns ());
-                                                                                      }
-                                                                                  }
-
-                                                                                  foreach (View view in enumerable)
-                                                                                  {
-                                                                                      var peer = (Shortcut)view;
-                                                                                      peer.MinimumKeyTextSize = max;
-                                                                                  }
+                                                                                  AlignKeys (align);
                                                                               }
                                                                           };
+
+
         Application.Top.Add (alignKeysShortcut);
 
         var commandFirstShortcut = new Shortcut
@@ -136,9 +115,7 @@ public class Shortcuts : Scenario
                                                                                                       $"{commandFirstShortcut.Id}.CommandView.CheckedStateChanging: {cb.Text}");
                                                                                      eventLog.MoveDown ();
 
-                                                                                     IEnumerable<View> toAlign =
-                                                                                         Application.Top.SubViews.Where (
-                                                                                          v => v is Shortcut { Width: not DimAbsolute });
+                                                                                     IEnumerable<View> toAlign = Application.Top.SubViews.OfType<Shortcut> ();
                                                                                      IEnumerable<View> enumerable = toAlign as View [] ?? toAlign.ToArray ();
 
                                                                                      foreach (View view in enumerable)
@@ -166,8 +143,8 @@ public class Shortcuts : Scenario
             Y = Pos.Bottom (commandFirstShortcut),
             Width = Dim.Fill ()! - Dim.Width (eventLog),
             Key = Key.F4,
-            HelpText = "Changes all Command.CanFocus",
-            CommandView = new CheckBox { Text = "_CanFocus" }
+            HelpText = "Changes all CommandView.CanFocus",
+            CommandView = new CheckBox { Text = "_CommandView.CanFocus" },
         };
 
         ((CheckBox)canFocusShortcut.CommandView).CheckedStateChanging += (s, e) =>
@@ -179,13 +156,7 @@ public class Shortcuts : Scenario
 
                                                                                  //cb.CanFocus = e.NewValue == CheckState.Checked;
 
-                                                                                 foreach (Shortcut peer in Application.Top.SubViews.Where (v => v is Shortcut)!)
-                                                                                 {
-                                                                                     if (peer.CanFocus)
-                                                                                     {
-                                                                                         peer.CommandView.CanFocus = e.Result == CheckState.Checked;
-                                                                                     }
-                                                                                 }
+                                                                                 SetCanFocus (e.Result == CheckState.Checked);
                                                                              }
                                                                          };
         Application.Top.Add (canFocusShortcut);
@@ -224,37 +195,39 @@ public class Shortcuts : Scenario
 
         Application.Top.Add (buttonShortcut);
 
-        var radioGroupShortcut = new Shortcut
+        var optionSelectorShortcut = new Shortcut
         {
-            Id = "radioGroupShortcut",
+            Id = "optionSelectorShortcut",
+            HelpText = "Option Selector",
             X = 0,
             Y = Pos.Bottom (buttonShortcut),
             Key = Key.F2,
             Width = Dim.Fill ()! - Dim.Width (eventLog),
-            CommandView = new RadioGroup
+            CommandView = new OptionSelector ()
             {
                 Orientation = Orientation.Vertical,
-                RadioLabels = ["O_ne", "T_wo", "Th_ree", "Fo_ur"]
-            }
+                Labels = ["O_ne", "T_wo", "Th_ree", "Fo_ur"],
+                HighlightStates = MouseState.None,
+            },
         };
 
-        ((RadioGroup)radioGroupShortcut.CommandView).SelectedItemChanged += (o, args) =>
-                                                                            {
-                                                                                if (o is { })
+        ((OptionSelector)optionSelectorShortcut.CommandView).ValueChanged += (o, args) =>
                                                                                 {
-                                                                                    eventSource.Add (
-                                                                                                     $"SelectedItemChanged: {o.GetType ().Name} - {args.SelectedItem}");
-                                                                                    eventLog.MoveDown ();
-                                                                                }
-                                                                            };
+                                                                                    if (o is { })
+                                                                                    {
+                                                                                        eventSource.Add (
+                                                                                                         $"ValueChanged: {o.GetType ().Name} - {args.Value}");
+                                                                                        eventLog.MoveDown ();
+                                                                                    }
+                                                                                };
 
-        Application.Top.Add (radioGroupShortcut);
+        Application.Top.Add (optionSelectorShortcut);
 
         var sliderShortcut = new Shortcut
         {
             Id = "sliderShortcut",
             X = 0,
-            Y = Pos.Bottom (radioGroupShortcut),
+            Y = Pos.Bottom (optionSelectorShortcut),
             Width = Dim.Fill ()! - Dim.Width (eventLog),
             HelpText = "Sliders work!",
             CommandView = new Slider<string>
@@ -277,12 +250,34 @@ public class Shortcuts : Scenario
 
         Application.Top.Add (sliderShortcut);
 
+        ListView listView = new ListView ()
+        {
+            Height = Dim.Auto (),
+            Width = Dim.Auto (),
+            Title = "ListView",
+            BorderStyle = LineStyle.Single
+        };
+        listView.EnableForDesign ();
+
+        var listViewShortcut = new Shortcut ()
+        {
+            Id = "listViewShortcut",
+            X = 0,
+            Y = Pos.Bottom (sliderShortcut),
+            Width = Dim.Fill ()! - Dim.Width (eventLog),
+            HelpText = "A ListView with Border",
+            CommandView = listView,
+            Key = Key.F5.WithCtrl,
+        };
+
+        Application.Top.Add (listViewShortcut);
+
         var noCommandShortcut = new Shortcut
         {
             Id = "noCommandShortcut",
             X = 0,
-            Y = Pos.Bottom (sliderShortcut),
-            Width = Dim.Width (sliderShortcut),
+            Y = Pos.Bottom (listViewShortcut),
+            Width = Dim.Width (listViewShortcut),
             HelpText = "No Command",
             Key = Key.D0
         };
@@ -321,13 +316,15 @@ public class Shortcuts : Scenario
             Id = "framedShortcut",
             X = 0,
             Y = Pos.Bottom (noHelpShortcut) + 1,
-            Title = "Framed",
+            Width = Dim.Width (noHelpShortcut),
+            Title = "Framed Shortcut",
             Key = Key.K.WithCtrl,
-            Text = "Resize frame",
+            Text = "Help: You can resize this",
             BorderStyle = LineStyle.Dotted,
             Arrangement = ViewArrangement.RightResizable | ViewArrangement.BottomResizable
         };
-        framedShortcut.Orientation = Orientation.Horizontal;
+        framedShortcut.Border!.Settings = BorderSettings.Title;
+        //framedShortcut.Orientation = Orientation.Horizontal;
 
         if (framedShortcut.Padding is { })
         {
@@ -337,12 +334,12 @@ public class Shortcuts : Scenario
 
         if (framedShortcut.CommandView.Margin is { })
         {
-            framedShortcut.CommandView.Margin!.SchemeName = framedShortcut.CommandView.SchemeName = "Error";
-            framedShortcut.HelpView.Margin!.SchemeName = framedShortcut.HelpView.SchemeName = "Dialog";
-            framedShortcut.KeyView.Margin!.SchemeName = framedShortcut.KeyView.SchemeName = "Menu";
+            framedShortcut.CommandView.SchemeName = framedShortcut.CommandView.SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Dialog);
+            framedShortcut.HelpView.SchemeName = framedShortcut.HelpView.SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Error);
+            framedShortcut.KeyView.SchemeName = framedShortcut.KeyView.SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Base);
         }
 
-        framedShortcut.SchemeName = "TopLevel";
+        framedShortcut.SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Toplevel);
         Application.Top.Add (framedShortcut);
 
         // Horizontal
@@ -504,7 +501,7 @@ public class Shortcuts : Scenario
                                                   eventSource.Add (
                                                                    $"{shortcut!.Id}.CommandView.Selecting: {shortcut!.CommandView.Text} {shortcut!.CommandView.GetType ().Name}");
                                                   eventLog.MoveDown ();
-                                                  args.Handled = true;
+                                                  //args.Handled = true;
                                               };
 
             shortcut.Accepting += (o, args) =>
@@ -513,7 +510,7 @@ public class Shortcuts : Scenario
                                       eventLog.MoveDown ();
 
                                       // We don't want this to exit the Scenario
-                                      args.Handled = true;
+                                      //args.Handled = true;
                                   };
 
             shortcut.CommandView.Accepting += (o, args) =>
@@ -523,6 +520,46 @@ public class Shortcuts : Scenario
                                                   eventLog.MoveDown ();
                                               };
         }
+
+        SetCanFocus (false);
+
+        AlignKeys (true);
+
+        return;
+
+        void SetCanFocus (bool canFocus)
+        {
+            foreach (Shortcut peer in Application.Top!.SubViews.OfType<Shortcut> ())
+            {
+                if (peer.CanFocus)
+                {
+                    peer.CommandView.CanFocus = canFocus;
+                }
+            }
+        }
+
+        void AlignKeys (bool align)
+        {
+            var max = 0;
+
+            IEnumerable<Shortcut> toAlign = Application.Top!.SubViews.OfType<Shortcut> ().Where(s => !s.Y.Has<PosAnchorEnd>(out _)).Cast<Shortcut>();
+            IEnumerable<Shortcut> enumerable = toAlign as Shortcut [] ?? toAlign.ToArray ();
+
+            if (align)
+            {
+                max = (from Shortcut? peer in enumerable
+                       select peer!.Key.ToString ().GetColumns ()).Prepend (max)
+                                                                  .Max ();
+
+                max = enumerable.Select (peer => peer.KeyView.Text.GetColumns ()).Prepend (max).Max ();
+            }
+
+            foreach (Shortcut shortcut in enumerable)
+            {
+                Shortcut peer = shortcut;
+                peer.MinimumKeyTextSize = max;
+            }
+        }
     }
 
     private void Button_Clicked (object? sender, CommandEventArgs e)

+ 10 - 10
Examples/UICatalog/Scenarios/TextAlignmentAndDirection.cs

@@ -492,18 +492,18 @@ public class TextAlignmentAndDirection : Scenario
 
         // JUSTIFY OPTIONS
 
-        var justifyOptions = new RadioGroup
+        var justifyOptions = new OptionSelector
         {
             X = Pos.Left (justifyCheckbox) + 1,
             Y = Pos.Y (justifyCheckbox) + 1,
             Width = Dim.Fill (9),
-            RadioLabels = ["Current direction", "Opposite direction", "FIll Both"],
+            Labels = ["Current direction", "Opposite direction", "FIll Both"],
             Enabled = false
         };
 
         justifyCheckbox.CheckedStateChanging += (s, e) => ToggleJustify (e.Result != CheckState.Checked);
 
-        justifyOptions.SelectedItemChanged += (s, e) => { ToggleJustify (false, true); };
+        justifyOptions.ValueChanged += (_, _) => { ToggleJustify (false, true); };
 
         app.Add (justifyOptions);
 
@@ -541,17 +541,17 @@ public class TextAlignmentAndDirection : Scenario
 
         List<TextDirection> directionsEnum = Enum.GetValues (typeof (TextDirection)).Cast<TextDirection> ().ToList ();
 
-        var directionOptions = new RadioGroup
+        var directionOptions = new OptionSelector
         {
             X = Pos.Right (container) + 1,
             Y = Pos.Bottom (wrapCheckbox) + 1,
             Width = Dim.Fill (10),
             Height = Dim.Fill (1),
             HotKeySpecifier = (Rune)'\xffff',
-            RadioLabels = directionsEnum.Select (e => e.ToString ()).ToArray ()
+            Labels = directionsEnum.Select (e => e.ToString ()).ToArray ()
         };
 
-        directionOptions.SelectedItemChanged += (s, ev) =>
+        directionOptions.ValueChanged += (s, ev) =>
                                                 {
                                                     bool justChecked = justifyCheckbox.CheckedState == CheckState.Checked;
 
@@ -560,9 +560,9 @@ public class TextAlignmentAndDirection : Scenario
                                                         ToggleJustify (true);
                                                     }
 
-                                                    foreach (View v in multiLineLabels)
+                                                    foreach (View v in multiLineLabels.Where (v => ev.Value is { }))
                                                     {
-                                                        v.TextDirection = (TextDirection)ev.SelectedItem;
+                                                        v.TextDirection = (TextDirection)ev.Value!.Value;
                                                     }
 
                                                     if (justChecked)
@@ -612,7 +612,7 @@ public class TextAlignmentAndDirection : Scenario
 
                     if (TextFormatter.IsVerticalDirection (t.TextDirection))
                     {
-                        switch (justifyOptions.SelectedItem)
+                        switch (justifyOptions.Value)
                         {
                             case 0:
                                 t.VerticalTextAlignment = Alignment.Fill;
@@ -630,7 +630,7 @@ public class TextAlignmentAndDirection : Scenario
                     }
                     else
                     {
-                        switch (justifyOptions.SelectedItem)
+                        switch (justifyOptions.Value)
                         {
                             case 0:
                                 t.TextAlignment = Alignment.Fill;

+ 6 - 6
Examples/UICatalog/Scenarios/Themes.cs

@@ -23,26 +23,26 @@ public sealed class Themes : Scenario
         };
 
         string[]  options = ThemeManager.GetThemeNames ().Select (option => option = "_" + option).ToArray ();
-        RadioGroup themeOptionSelector = new ()
+        OptionSelector themeOptionSelector = new ()
         {
             Title = "_Themes",
             BorderStyle = LineStyle.Rounded,
             Width = Dim.Auto (),
             Height = Dim.Auto (),
-            RadioLabels= options,
-            SelectedItem = ThemeManager.GetThemeNames ().IndexOf (ThemeManager.Theme)
+            Labels= options,
+            Value = ThemeManager.GetThemeNames ().IndexOf (ThemeManager.Theme)
         };
         themeOptionSelector.Border!.Thickness = new (0, 1, 0, 0);
         themeOptionSelector.Margin!.Thickness = new (0, 0, 1, 0);
 
-        themeOptionSelector.SelectedItemChanged += (sender, args) =>
+        themeOptionSelector.ValueChanged += (sender, args) =>
                                              {
-                                                 RadioGroup? optionSelector = sender as RadioGroup;
+                                                 OptionSelector? optionSelector = sender as OptionSelector;
                                                  if (optionSelector is null)
                                                  {
                                                      return;
                                                  }
-                                                 var newTheme = optionSelector!.RadioLabels! [(int)args.SelectedItem!] as string;
+                                                 var newTheme = optionSelector!.Labels! [(int)args.Value!] as string;
                                                  // strip off the leading underscore
                                                  ThemeManager.Theme = newTheme!.Substring (1);
                                                  ConfigurationManager.Apply ();

+ 5 - 5
Examples/UICatalog/Scenarios/Unicode.cs

@@ -176,19 +176,19 @@ public class UnicodeInMenu : Scenario
         };
         appWindow.Add (listView);
 
-        label = new () { X = Pos.X (label), Y = Pos.Bottom (listView) + 1, Text = "RadioGroup:" };
+        label = new () { X = Pos.X (label), Y = Pos.Bottom (listView) + 1, Text = "OptionSelector:" };
         appWindow.Add (label);
 
-        var radioGroup = new RadioGroup
+        var optionSelector = new OptionSelector
         {
             X = 20,
             Y = Pos.Y (label),
             Width = Dim.Percent (60),
-            RadioLabels = ["item #1", gitString, "Со_хранить", "𝔽𝕆𝕆𝔹𝔸ℝ"]
+            Labels = new [] { "item #1", gitString, "Со_хранить", "𝔽𝕆𝕆𝔹𝔸ℝ" }
         };
-        appWindow.Add (radioGroup);
+        appWindow.Add (optionSelector);
 
-        label = new () { X = Pos.X (label), Y = Pos.Bottom (radioGroup) + 1, Text = "TextField:" };
+        label = new () { X = Pos.X (label), Y = Pos.Bottom (optionSelector) + 1, Text = "TextField:" };
         appWindow.Add (label);
 
         var textField = new TextField

+ 6 - 6
Examples/UICatalog/Scenarios/Wizards.cs

@@ -162,11 +162,11 @@ public class Wizards : Scenario
                                            firstStep.HelpText =
                                                "This is the End User License Agreement.\n\n\n\n\n\nThis is a test of the emergency broadcast system. This is a test of the emergency broadcast system.\nThis is a test of the emergency broadcast system.\n\n\nThis is a test of the emergency broadcast system.\n\nThis is a test of the emergency broadcast system.\n\n\n\nThe end of the EULA.";
 
-                                           RadioGroup radioGroup = new ()
+                                           OptionSelector optionSelector = new ()
                                            {
-                                               RadioLabels = ["_One", "_Two", "_3"]
+                                               Labels = ["_One", "_Two", "_3"]
                                            };
-                                           firstStep.Add (radioGroup);
+                                           firstStep.Add (optionSelector);
 
                                            wizard.AddStep (firstStep);
 
@@ -184,12 +184,12 @@ public class Wizards : Scenario
                                                Text = "Press Me to Rename Step", X = Pos.Right (buttonLbl), Y = Pos.Top (buttonLbl)
                                            };
 
-                                           RadioGroup radioGroup2 = new ()
+                                           OptionSelector optionSelecor2 = new ()
                                            {
-                                               RadioLabels = ["_A", "_B", "_C"],
+                                               Labels = ["_A", "_B", "_C"],
                                                Orientation = Orientation.Horizontal
                                            };
-                                           secondStep.Add (radioGroup2);
+                                           secondStep.Add (optionSelecor2);
 
                                            button.Accepting += (s, e) =>
                                                             {

+ 52 - 44
Examples/UICatalog/UICatalogTop.cs

@@ -96,9 +96,9 @@ public class UICatalogTop : Toplevel
 
     private readonly MenuBarv2? _menuBar;
     private CheckBox? _force16ColorsMenuItemCb;
-    private OptionSelector? _themesRg;
-    private OptionSelector? _topSchemeRg;
-    private OptionSelector? _logLevelRg;
+    private OptionSelector? _themesSelector;
+    private OptionSelector? _topSchemesSelector;
+    private OptionSelector? _logLevelSelector;
     private FlagSelector<ViewDiagnosticFlags>? _diagnosticFlagsSelector;
     private CheckBox? _disableMouseCb;
 
@@ -126,13 +126,13 @@ public class UICatalogTop : Toplevel
                                           [
                                               new MenuItemv2 (
                                                               "_Documentation",
-                                                              "",
+                                                              "API docs",
                                                               () => OpenUrl ("https://gui-cs.github.io/Terminal.Gui"),
                                                               Key.F1
                                                              ),
                                               new MenuItemv2 (
                                                               "_README",
-                                                              "",
+                                                              "Project readme",
                                                               () => OpenUrl ("https://github.com/gui-cs/Terminal.Gui"),
                                                               Key.F2
                                                              ),
@@ -163,7 +163,10 @@ public class UICatalogTop : Toplevel
             _force16ColorsMenuItemCb = new ()
             {
                 Title = "Force _16 Colors",
-                CheckedState = Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked
+                CheckedState = Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked,
+                // Best practice for CheckBoxes in menus is to disable focus and highlight states
+                CanFocus = false,
+                HighlightStates = MouseState.None
             };
 
             _force16ColorsMenuItemCb.CheckedStateChanging += (sender, args) =>
@@ -194,23 +197,25 @@ public class UICatalogTop : Toplevel
 
             if (ConfigurationManager.IsEnabled)
             {
-                _themesRg = new ()
+                _themesSelector = new ()
                 {
-                    HighlightStates = Terminal.Gui.ViewBase.MouseState.None,
+                    // HighlightStates = MouseState.In,
+                    CanFocus = true,
+                    // InvertFocusAttribute = true
                 };
 
-                _themesRg.SelectedItemChanged += (_, args) =>
+                _themesSelector.ValueChanged += (_, args) =>
                                                  {
-                                                     if (args.SelectedItem is null)
+                                                     if (args.Value is null)
                                                      {
                                                          return;
                                                      }
-                                                     ThemeManager.Theme = ThemeManager.GetThemeNames () [args.SelectedItem!.Value];
+                                                     ThemeManager.Theme = ThemeManager.GetThemeNames () [(int)args.Value];
                                                  };
 
                 var menuItem = new MenuItemv2
                 {
-                    CommandView = _themesRg,
+                    CommandView = _themesSelector,
                     HelpText = "Cycle Through Themes",
                     Key = Key.T.WithCtrl
                 };
@@ -218,18 +223,18 @@ public class UICatalogTop : Toplevel
 
                 menuItems.Add (new Line ());
 
-                _topSchemeRg = new ()
+                _topSchemesSelector = new ()
                 {
-                    HighlightStates = Terminal.Gui.ViewBase.MouseState.None,
+                    //  HighlightStates = MouseState.In,
                 };
 
-                _topSchemeRg.SelectedItemChanged += (_, args) =>
+                _topSchemesSelector.ValueChanged += (_, args) =>
                                                     {
-                                                        if (args.SelectedItem is null)
+                                                        if (args.Value is null)
                                                         {
                                                             return;
                                                         }
-                                                        CachedTopLevelScheme = SchemeManager.GetSchemesForCurrentTheme ()!.Keys.ToArray () [args.SelectedItem!.Value];
+                                                        CachedTopLevelScheme = SchemeManager.GetSchemesForCurrentTheme ()!.Keys.ToArray () [(int)args.Value];
                                                         SchemeName = CachedTopLevelScheme;
                                                         SetNeedsDraw ();
                                                     };
@@ -241,7 +246,7 @@ public class UICatalogTop : Toplevel
                                    [
                                        new ()
                                        {
-                                           CommandView = _topSchemeRg,
+                                           CommandView = _topSchemesSelector,
                                            HelpText = "Cycle Through schemes",
                                            Key = Key.S.WithCtrl
                                        }
@@ -269,12 +274,12 @@ public class UICatalogTop : Toplevel
 
             _diagnosticFlagsSelector = new ()
             {
-                CanFocus = true,
-                Styles = FlagSelectorStyles.ShowNone,
-                HighlightStates = Terminal.Gui.ViewBase.MouseState.None,
+                Styles = SelectorStyles.ShowNoneFlag,
+                CanFocus = true
+
             };
             _diagnosticFlagsSelector.UsedHotKeys.Add (Key.D);
-            _diagnosticFlagsSelector.AssignHotKeysToCheckBoxes = true;
+            _diagnosticFlagsSelector.AssignHotKeys = true;
             _diagnosticFlagsSelector.Value = Diagnostics;
             _diagnosticFlagsSelector.ValueChanged += (sender, args) =>
                                                      {
@@ -294,7 +299,10 @@ public class UICatalogTop : Toplevel
             _disableMouseCb = new ()
             {
                 Title = "_Disable Mouse",
-                CheckedState = Application.IsMouseDisabled ? CheckState.Checked : CheckState.UnChecked
+                CheckedState = Application.IsMouseDisabled ? CheckState.Checked : CheckState.UnChecked,
+                // Best practice for CheckBoxes in menus is to disable focus and highlight states
+                CanFocus = false,
+                HighlightStates = MouseState.None
             };
 
             _disableMouseCb.CheckedStateChanged += (_, args) => { Application.IsMouseDisabled = args.Value == CheckState.Checked; };
@@ -315,17 +323,17 @@ public class UICatalogTop : Toplevel
 
             LogLevel [] logLevels = Enum.GetValues<LogLevel> ();
 
-            _logLevelRg = new ()
+            _logLevelSelector = new ()
             {
-                AssignHotKeysToCheckBoxes = true,
-                Options = Enum.GetNames<LogLevel> (),
-                SelectedItem = logLevels.ToList ().IndexOf (Enum.Parse<LogLevel> (UICatalog.Options.DebugLogLevel)),
-                HighlightStates = Terminal.Gui.ViewBase.MouseState.In
+                AssignHotKeys = true,
+                Labels = Enum.GetNames<LogLevel> (),
+                Value = logLevels.ToList ().IndexOf (Enum.Parse<LogLevel> (UICatalog.Options.DebugLogLevel)),
+                // HighlightStates = MouseState.In,
             };
 
-            _logLevelRg.SelectedItemChanged += (_, args) =>
+            _logLevelSelector.ValueChanged += (_, args) =>
             {
-                UICatalog.Options = UICatalog.Options with { DebugLogLevel = Enum.GetName (logLevels [args.SelectedItem!.Value])! };
+                UICatalog.Options = UICatalog.Options with { DebugLogLevel = Enum.GetName (logLevels [args.Value!.Value])! };
 
                 UICatalog.LogLevelSwitch.MinimumLevel =
                     UICatalog.LogLevelToLogEventLevel (Enum.Parse<LogLevel> (UICatalog.Options.DebugLogLevel));
@@ -334,7 +342,7 @@ public class UICatalogTop : Toplevel
             menuItems.Add (
                            new MenuItemv2
                            {
-                               CommandView = _logLevelRg,
+                               CommandView = _logLevelSelector,
                                HelpText = "Cycle Through Log Levels",
                                Key = Key.L.WithCtrl
                            });
@@ -356,27 +364,27 @@ public class UICatalogTop : Toplevel
 
     private void UpdateThemesMenu ()
     {
-        if (_themesRg is null)
+        if (_themesSelector is null)
         {
             return;
         }
 
-        _themesRg.SelectedItem = null;
-        _themesRg.AssignHotKeysToCheckBoxes = true;
-        _themesRg.UsedHotKeys.Clear ();
-        _themesRg.Options = ThemeManager.GetThemeNames ();
-        _themesRg.SelectedItem =ThemeManager.GetThemeNames ().IndexOf (ThemeManager.GetCurrentThemeName ());
+        _themesSelector.Value = null;
+        _themesSelector.AssignHotKeys = true;
+        _themesSelector.UsedHotKeys.Clear ();
+        _themesSelector.Labels = ThemeManager.GetThemeNames ().ToArray ();
+        _themesSelector.Value = ThemeManager.GetThemeNames ().IndexOf (ThemeManager.GetCurrentThemeName ());
 
-        if (_topSchemeRg is null)
+        if (_topSchemesSelector is null)
         {
             return;
         }
 
-        _topSchemeRg.AssignHotKeysToCheckBoxes = true;
-        _topSchemeRg.UsedHotKeys.Clear ();
-        int? selectedScheme = _topSchemeRg.SelectedItem;
-        _topSchemeRg.Options = SchemeManager.GetSchemeNames ();
-        _topSchemeRg.SelectedItem = selectedScheme;
+        _topSchemesSelector.AssignHotKeys = true;
+        _topSchemesSelector.UsedHotKeys.Clear ();
+        int? selectedScheme = _topSchemesSelector.Value;
+        _topSchemesSelector.Labels = SchemeManager.GetSchemeNames ().ToArray ();
+        _topSchemesSelector.Value = selectedScheme;
 
         if (CachedTopLevelScheme is null || !SchemeManager.GetSchemeNames ().Contains (CachedTopLevelScheme))
         {
@@ -387,7 +395,7 @@ public class UICatalogTop : Toplevel
         // if the item is in bounds then select it
         if (newSelectedItem >= 0 && newSelectedItem < SchemeManager.GetSchemeNames ().Count)
         {
-            _topSchemeRg.SelectedItem = newSelectedItem;
+            _topSchemesSelector.Value = newSelectedItem;
         }
     }
 

+ 0 - 1
README.md

@@ -1,4 +1,3 @@
-![.NET Core](https://github.com/gui-cs/Terminal.Gui/workflows/.NET%20Core/badge.svg?branch=v2_develop)
 [![Version](https://img.shields.io/nuget/v/Terminal.Gui.svg)](https://www.nuget.org/packages/Terminal.Gui)
 [![codecov](https://codecov.io/gh/gui-cs/Terminal.Gui/graph/badge.svg?token=1Ac9gyGtrj)](https://codecov.io/gh/gui-cs/Terminal.Gui)
 [![Downloads](https://img.shields.io/nuget/dt/Terminal.Gui)](https://www.nuget.org/packages/Terminal.Gui)

+ 1 - 0
Terminal.Gui/App/ApplicationImpl.cs

@@ -66,6 +66,7 @@ public partial class ApplicationImpl : IApplication
     }
 
     private IKeyboard? _keyboard;
+    private bool _stopAfterFirstIteration;
 
     /// <summary>
     ///     Handles keyboard input and key bindings at the Application level

+ 2 - 2
Terminal.Gui/Drawing/Glyphs.cs

@@ -57,11 +57,11 @@ public class Glyphs
     [ConfigurationProperty (Scope = typeof (ThemeScope))]
     public static Rune CheckStateNone { get; set; } = (Rune)'□'; // TODO: Verify this works as broadly as possible
 
-    /// <summary>Selected indicator  (e.g. for <see cref="ListView"/> and <see cref="RadioGroup"/>).</summary>
+    /// <summary>Selected indicator  (e.g. for <see cref="ListView"/> and <see cref="OptionSelector"/>).</summary>
     [ConfigurationProperty (Scope = typeof (ThemeScope))]
     public static Rune Selected { get; set; } = (Rune)'◉';
 
-    /// <summary>Not Selected indicator (e.g. for <see cref="ListView"/> and <see cref="RadioGroup"/>).</summary>
+    /// <summary>Not Selected indicator (e.g. for <see cref="ListView"/> and <see cref="OptionSelector"/>).</summary>
     [ConfigurationProperty (Scope = typeof (ThemeScope))]
     public static Rune UnSelected { get; set; } = (Rune)'○';
 

+ 1 - 0
Terminal.Gui/Drivers/WindowsDriver/WindowsOutput.cs

@@ -149,6 +149,7 @@ internal partial class WindowsOutput : OutputBase, IOutput
 
             // Force 16 colors if not in virtual terminal mode.
             Application.Force16Colors = true;
+
         }
 
         GetSize ();

+ 11 - 1
Terminal.Gui/Input/Mouse/MouseBinding.cs

@@ -11,7 +11,7 @@ public record struct MouseBinding : IInputBinding
 {
     /// <summary>Initializes a new instance.</summary>
     /// <param name="commands">The commands this mouse binding will invoke.</param>
-    /// <param name="mouseFlags">The mouse flags that trigger this binding.</param>
+    /// <param name="mouseFlags">The mouse flags that triggered this binding.</param>
     public MouseBinding (Command [] commands, MouseFlags mouseFlags)
     {
         Commands = commands;
@@ -22,6 +22,16 @@ public record struct MouseBinding : IInputBinding
         };
     }
 
+
+    /// <summary>Initializes a new instance.</summary>
+    /// <param name="commands">The commands this mouse binding will invoke.</param>
+    /// <param name="args">The mouse event that triggered this binding.</param>
+    public MouseBinding (Command [] commands, MouseEventArgs args)
+    {
+        Commands = commands;
+        MouseEventArgs = args;
+    }
+
     /// <summary>The commands this binding will invoke.</summary>
     public Command [] Commands { get; set; }
 

+ 1 - 1
Terminal.Gui/ViewBase/View.Keyboard.cs

@@ -99,7 +99,7 @@ public partial class View // Keyboard APIs
 
     /// <summary>
     ///     Adds key bindings for the specified HotKey. Useful for views that contain multiple items that each have their
-    ///     own HotKey such as <see cref="RadioGroup"/>.
+    ///     own HotKey such as <see cref="OptionSelector"/>.
     /// </summary>
     /// <remarks>
     ///     <para>

+ 40 - 26
Terminal.Gui/Views/CheckBox.cs

@@ -26,22 +26,14 @@ public class CheckBox : View
 
         CanFocus = true;
 
-        // Select (Space key and single-click) - Advance state and raise Select event - DO NOT raise Accept
-        AddCommand (Command.Select, AdvanceAndSelect);
-
-        // Hotkey - Advance state and raise Select event - DO NOT raise Accept
-        AddCommand (Command.HotKey, ctx =>
-                                    {
-                                        if (RaiseHandlingHotKey (ctx) is true)
-                                        {
-                                            return true;
-                                        }
-                                        return AdvanceAndSelect (ctx);
-                                    });
-
-        // Accept (Enter key) - Raise Accept event - DO NOT advance state
-        AddCommand (Command.Accept, RaiseAccepting);
-
+        // Activate (Space key and single-click) - Raise Activate event and Advance
+        // - DO NOT raise Accept
+        // - DO NOT SetFocus
+        AddCommand (Command.Select, ActivateAndAdvance);
+
+        // Accept (Enter key and double-click) - Raise Accept event
+        // - DO NOT advance state
+        // The default Accept handler does that.
         MouseBindings.Add (MouseFlags.Button1DoubleClicked, Command.Accept);
 
         TitleChanged += Checkbox_TitleChanged;
@@ -49,20 +41,35 @@ public class CheckBox : View
         HighlightStates = DefaultHighlightStates;
     }
 
-    private bool? AdvanceAndSelect (ICommandContext? commandContext)
+    /// <inheritdoc />
+    protected override bool OnHandlingHotKey (CommandEventArgs args)
     {
-        bool? cancelled = AdvanceCheckState ();
-
-        if (cancelled is true)
+        // Invoke Activate on ourselves
+        if (InvokeCommand (Command.Select, args.Context) is true)
         {
+            // Default behavior for View is to set Focus on hotkey. We need to return
+            // true here to indicate Activate was handled. That will prevent the default
+            // behavior from setting focus, so we do it here.
+            SetFocus ();
             return true;
         }
+        return base.OnHandlingHotKey (args);
+    }
 
+    private bool? ActivateAndAdvance (ICommandContext? commandContext)
+    {
         if (RaiseSelecting (commandContext) is true)
         {
             return true;
         }
 
+        bool? cancelled = AdvanceCheckState ();
+
+        if (cancelled is true)
+        {
+            return true;
+        }
+
         return commandContext?.Command == Command.HotKey ? cancelled : cancelled is false;
     }
 
@@ -251,6 +258,13 @@ public class CheckBox : View
         return cancelled;
     }
 
+    /// <inheritdoc />
+    protected override bool OnClearingViewport ()
+    {
+        SetAttributeForRole (HasFocus ? VisualRole.Focus : VisualRole.Normal);
+        return base.OnClearingViewport ();
+    }
+
     /// <inheritdoc/>
     protected override void UpdateTextFormatterText ()
     {
@@ -292,11 +306,11 @@ public class CheckBox : View
     private Rune GetRadioGlyph ()
     {
         return CheckedState switch
-               {
-                   CheckState.Checked => Glyphs.Selected,
-                   CheckState.UnChecked => Glyphs.UnSelected,
-                   CheckState.None => Glyphs.Dot,
-                   _ => throw new ArgumentOutOfRangeException ()
-               };
+        {
+            CheckState.Checked => Glyphs.Selected,
+            CheckState.UnChecked => Glyphs.UnSelected,
+            CheckState.None => Glyphs.Dot,
+            _ => throw new ArgumentOutOfRangeException ()
+        };
     }
 }

+ 0 - 513
Terminal.Gui/Views/FlagSelector.cs

@@ -1,513 +0,0 @@
-#nullable enable
-
-namespace Terminal.Gui.Views;
-
-/// <summary>
-///     Provides a user interface for displaying and selecting non-mutually-exclusive flags.
-///     Flags can be set from a dictionary or directly from an enum type.
-/// </summary>
-public class FlagSelector : View, IOrientation, IDesignable
-{
-    /// <summary>
-    ///     Initializes a new instance of the <see cref="FlagSelector"/> class.
-    /// </summary>
-    public FlagSelector ()
-    {
-        CanFocus = true;
-
-        Width = Dim.Auto (DimAutoStyle.Content);
-        Height = Dim.Auto (DimAutoStyle.Content);
-
-        // ReSharper disable once UseObjectOrCollectionInitializer
-        _orientationHelper = new (this);
-        _orientationHelper.Orientation = Orientation.Vertical;
-
-        // Accept (Enter key or DoubleClick) - Raise Accept event - DO NOT advance state
-        AddCommand (Command.Accept, HandleAcceptCommand);
-
-        CreateCheckBoxes ();
-    }
-
-    private bool? HandleAcceptCommand (ICommandContext? ctx) { return RaiseAccepting (ctx); }
-
-    private uint? _value;
-
-    /// <summary>
-    /// Gets or sets the value of the selected flags.
-    /// </summary>
-    public uint? Value
-    {
-        get => _value;
-        set
-        {
-            if (_value == value)
-            {
-                return;
-            }
-
-            _value = value;
-
-            if (_value is null)
-            {
-                UncheckNone ();
-                UncheckAll ();
-            }
-            else
-            {
-                UpdateChecked ();
-            }
-
-            if (ValueEdit is { })
-            {
-                ValueEdit.Text = _value.ToString ();
-            }
-
-            RaiseValueChanged ();
-        }
-    }
-
-    private void RaiseValueChanged ()
-    {
-        OnValueChanged ();
-        if (Value.HasValue)
-        {
-            ValueChanged?.Invoke (this, new EventArgs<uint> (Value.Value));
-        }
-    }
-
-    /// <summary>
-    ///     Called when <see cref="Value"/> has changed.
-    /// </summary>
-    protected virtual void OnValueChanged () { }
-
-    /// <summary>
-    ///     Raised when <see cref="Value"/> has changed.
-    /// </summary>
-    public event EventHandler<EventArgs<uint>>? ValueChanged;
-
-    private FlagSelectorStyles _styles;
-
-    /// <summary>
-    /// Gets or sets the styles for the flag selector.
-    /// </summary>
-    public FlagSelectorStyles Styles
-    {
-        get => _styles;
-        set
-        {
-            if (_styles == value)
-            {
-                return;
-            }
-
-            _styles = value;
-
-            CreateCheckBoxes ();
-        }
-    }
-
-    /// <summary>
-    ///     Set the flags and flag names.
-    /// </summary>
-    /// <param name="flags"></param>
-    public virtual void SetFlags (IReadOnlyDictionary<uint, string> flags)
-    {
-        Flags = flags;
-        CreateCheckBoxes ();
-        UpdateChecked ();
-    }
-
-
-    /// <summary>
-    ///     Set the flags and flag names from an enum type.
-    /// </summary>
-    /// <typeparam name="TEnum">The enum type to extract flags from</typeparam>
-    /// <remarks>
-    ///     This is a convenience method that converts an enum to a dictionary of flag values and names.
-    ///     The enum values are converted to uint values and the enum names become the display text.
-    /// </remarks>
-    public void SetFlags<TEnum> () where TEnum : struct, Enum
-    {
-        // Convert enum names and values to a dictionary
-        Dictionary<uint, string> flagsDictionary = Enum.GetValues<TEnum> ()
-                                                       .ToDictionary (
-                                                                      f => Convert.ToUInt32 (f),
-                                                                      f => f.ToString ()
-                                                                     );
-
-        SetFlags (flagsDictionary);
-    }
-
-    /// <summary>
-    ///     Set the flags and flag names from an enum type with custom display names.
-    /// </summary>
-    /// <typeparam name="TEnum">The enum type to extract flags from</typeparam>
-    /// <param name="nameSelector">A function that converts enum values to display names</param>
-    /// <remarks>
-    ///     This is a convenience method that converts an enum to a dictionary of flag values and custom names.
-    ///     The enum values are converted to uint values and the display names are determined by the nameSelector function.
-    /// </remarks>
-    /// <example>
-    ///     <code>
-    ///        // Use enum values with custom display names
-    ///        var flagSelector = new FlagSelector ();
-    ///        flagSelector.SetFlags&lt;FlagSelectorStyles&gt;
-    ///             (f => f switch {
-    ///             FlagSelectorStyles.ShowNone => "Show None Value",
-    ///             FlagSelectorStyles.ShowValueEdit => "Show Value Editor",
-    ///             FlagSelectorStyles.All => "Everything",
-    ///             _ => f.ToString()
-    ///             });
-    ///     </code>
-    /// </example>
-    public void SetFlags<TEnum> (Func<TEnum, string> nameSelector) where TEnum : struct, Enum
-    {
-        // Convert enum values and custom names to a dictionary
-        Dictionary<uint, string> flagsDictionary = Enum.GetValues<TEnum> ()
-                                                       .ToDictionary (
-                                                                      f => Convert.ToUInt32 (f),
-                                                                      nameSelector
-                                                                     );
-
-        SetFlags (flagsDictionary);
-    }
-
-    private IReadOnlyDictionary<uint, string>? _flags;
-
-    /// <summary>
-    ///     Gets the flag values and names.
-    /// </summary>
-    public IReadOnlyDictionary<uint, string>? Flags
-    {
-        get => _flags;
-        internal set
-        {
-            _flags = value;
-
-            if (_value is null)
-            {
-                Value = Convert.ToUInt16 (_flags?.Keys.ElementAt (0));
-            }
-        }
-    }
-
-    private TextField? ValueEdit { get; set; }
-
-    private bool _assignHotKeysToCheckBoxes;
-
-    /// <summary>
-    ///     If <see langword="true"/> the CheckBoxes will each be automatically assigned a hotkey.
-    ///     <see cref="UsedHotKeys"/> will be used to ensure unique keys are assigned. Set <see cref="UsedHotKeys"/>
-    ///     before setting <see cref="Flags"/> with any hotkeys that may conflict with other Views.
-    /// </summary>
-    public bool AssignHotKeysToCheckBoxes
-    {
-        get => _assignHotKeysToCheckBoxes;
-        set
-        {
-            if (_assignHotKeysToCheckBoxes == value)
-            {
-                return;
-            }
-            _assignHotKeysToCheckBoxes = value;
-            CreateCheckBoxes ();
-            UpdateChecked ();
-        }
-    }
-
-    /// <summary>
-    ///     Gets the list of hotkeys already used by the CheckBoxes or that should not be used if
-    ///     <see cref="AssignHotKeysToCheckBoxes"/>
-    ///     is enabled.
-    /// </summary>
-    public List<Key> UsedHotKeys { get; } = [];
-
-    private void CreateCheckBoxes ()
-    {
-        if (Flags is null)
-        {
-            return;
-        }
-
-        foreach (CheckBox cb in RemoveAll<CheckBox> ())
-        {
-            cb.Dispose ();
-        }
-
-        if (Styles.HasFlag (FlagSelectorStyles.ShowNone) && !Flags.ContainsKey (0))
-        {
-            Add (CreateCheckBox ("None", 0));
-        }
-
-        for (var index = 0; index < Flags.Count; index++)
-        {
-            if (!Styles.HasFlag (FlagSelectorStyles.ShowNone) && Flags.ElementAt (index).Key == 0)
-            {
-                continue;
-            }
-
-            Add (CreateCheckBox (Flags.ElementAt (index).Value, Flags.ElementAt (index).Key));
-        }
-
-        if (Styles.HasFlag (FlagSelectorStyles.ShowValueEdit))
-        {
-            ValueEdit = new ()
-            {
-                Id = "valueEdit",
-                CanFocus = false,
-                Text = Value.ToString (),
-                Width = 5,
-                ReadOnly = true,
-            };
-
-            Add (ValueEdit);
-        }
-
-        SetLayout ();
-
-        return;
-
-
-    }
-
-    /// <summary>
-    /// 
-    /// </summary>
-    /// <param name="name"></param>
-    /// <param name="flag"></param>
-    /// <returns></returns>
-    protected virtual CheckBox CreateCheckBox (string name, uint flag)
-    {
-        string nameWithHotKey = name;
-        if (AssignHotKeysToCheckBoxes)
-        {
-            // Find the first char in label that is [a-z], [A-Z], or [0-9]
-            for (var i = 0; i < name.Length; i++)
-            {
-                char c = char.ToLowerInvariant (name [i]);
-                if (UsedHotKeys.Contains (new (c)) || !char.IsAsciiLetterOrDigit (c))
-                {
-                    continue;
-                }
-
-                if (char.IsAsciiLetterOrDigit (c))
-                {
-                    char? hotChar = c;
-                    nameWithHotKey = name.Insert (i, HotKeySpecifier.ToString ());
-                    UsedHotKeys.Add (new (hotChar));
-
-                    break;
-                }
-            }
-        }
-
-        var checkbox = new CheckBox
-        {
-            CanFocus = true,
-            Title = nameWithHotKey,
-            Id = name,
-            Data = flag,
-            HighlightStates = ViewBase.MouseState.In
-        };
-
-        checkbox.GettingAttributeForRole += (_, e) =>
-                                       {
-                                           if (SuperView is { HasFocus: false })
-                                           {
-                                               return;
-                                           }
-
-                                           switch (e.Role)
-                                           {
-                                               case VisualRole.Normal:
-                                                   e.Handled = true;
-
-                                                   if (!HasFocus)
-                                                   {
-                                                       e.Result = GetAttributeForRole (VisualRole.Focus);
-                                                   }
-                                                   else
-                                                   {
-                                                       // If _scheme was set, it's because of Hover
-                                                       if (checkbox.HasScheme)
-                                                       {
-                                                           e.Result = checkbox.GetAttributeForRole (VisualRole.Normal);
-                                                       }
-                                                       else
-                                                       {
-                                                           e.Result = GetAttributeForRole (VisualRole.Normal);
-                                                       }
-                                                   }
-
-                                                   break;
-
-                                               case VisualRole.HotNormal:
-                                                   e.Handled = true;
-                                                   if (!HasFocus)
-                                                   {
-                                                       e.Result = GetAttributeForRole (VisualRole.HotFocus);
-                                                   }
-                                                   else
-                                                   {
-                                                       e.Result = GetAttributeForRole (VisualRole.HotNormal);
-                                                   }
-
-                                                   break;
-                                           }
-                                       };
-
-        //checkbox.GettingFocusColor += (_, e) =>
-        //                                  {
-        //                                      if (SuperView is { HasFocus: true })
-        //                                      {
-        //                                          e.Cancel = true;
-        //                                          if (!HasFocus)
-        //                                          {
-        //                                              e.NewValue = GetAttributeForRole (VisualRole.Normal);
-        //                                          }
-        //                                          else
-        //                                          {
-        //                                              e.NewValue = GetAttributeForRole (VisualRole.Focus);
-        //                                          }
-        //                                      }
-        //                                  };
-
-        checkbox.Selecting += (sender, args) =>
-                              {
-                                  if (RaiseSelecting (args.Context) is true)
-                                  {
-                                      args.Handled = true;
-
-                                      return;
-                                  }
-                                  ;
-
-                                  if (RaiseAccepting (args.Context) is true)
-                                  {
-                                      args.Handled = true;
-                                  }
-                              };
-
-        checkbox.CheckedStateChanged += (sender, args) =>
-                                        {
-                                            uint? newValue = Value;
-
-                                            if (checkbox.CheckedState == CheckState.Checked)
-                                            {
-                                                if (flag == default!)
-                                                {
-                                                    newValue = 0;
-                                                }
-                                                else
-                                                {
-                                                    newValue = newValue | flag;
-                                                }
-                                            }
-                                            else
-                                            {
-                                                newValue = newValue & ~flag;
-                                            }
-
-                                            Value = newValue;
-                                        };
-
-        return checkbox;
-    }
-    private void SetLayout ()
-    {
-        foreach (View sv in SubViews)
-        {
-            if (Orientation == Orientation.Vertical)
-            {
-                sv.X = 0;
-                sv.Y = Pos.Align (Alignment.Start);
-            }
-            else
-            {
-                sv.X = Pos.Align (Alignment.Start);
-                sv.Y = 0;
-                sv.Margin!.Thickness = new (0, 0, 1, 0);
-            }
-        }
-    }
-
-    private void UncheckAll ()
-    {
-        foreach (CheckBox cb in SubViews.OfType<CheckBox> ().Where (sv => (uint)(sv.Data ?? default!) != default!))
-        {
-            cb.CheckedState = CheckState.UnChecked;
-        }
-    }
-
-    private void UncheckNone ()
-    {
-        foreach (CheckBox cb in SubViews.OfType<CheckBox> ().Where (sv => sv.Title != "None"))
-        {
-            cb.CheckedState = CheckState.UnChecked;
-        }
-    }
-
-    private void UpdateChecked ()
-    {
-        foreach (CheckBox cb in SubViews.OfType<CheckBox> ())
-        {
-            var flag = (uint)(cb.Data ?? throw new InvalidOperationException ("ComboBox.Data must be set"));
-
-            // If this flag is set in Value, check the checkbox. Otherwise, uncheck it.
-            if (flag == 0 && Value != 0)
-            {
-                cb.CheckedState = CheckState.UnChecked;
-            }
-            else
-            {
-                cb.CheckedState = (Value & flag) == flag ? CheckState.Checked : CheckState.UnChecked;
-            }
-        }
-    }
-
-
-    #region IOrientation
-
-    /// <summary>
-    ///     Gets or sets the <see cref="Orientation"/> for this <see cref="RadioGroup"/>. The default is
-    ///     <see cref="Orientation.Vertical"/>.
-    /// </summary>
-    public Orientation Orientation
-    {
-        get => _orientationHelper.Orientation;
-        set => _orientationHelper.Orientation = value;
-    }
-
-    private readonly OrientationHelper _orientationHelper;
-
-#pragma warning disable CS0067 // The event is never used
-    /// <inheritdoc/>
-    public event EventHandler<CancelEventArgs<Orientation>>? OrientationChanging;
-
-    /// <inheritdoc/>
-    public event EventHandler<EventArgs<Orientation>>? OrientationChanged;
-#pragma warning restore CS0067 // The event is never used
-
-    /// <summary>Called when <see cref="Orientation"/> has changed.</summary>
-    /// <param name="newOrientation"></param>
-    public void OnOrientationChanged (Orientation newOrientation) { SetLayout (); }
-
-    #endregion IOrientation
-
-    /// <inheritdoc/>
-    public bool EnableForDesign ()
-    {
-        Styles = FlagSelectorStyles.All;
-        SetFlags<FlagSelectorStyles> (
-                                      f => f switch
-                                           {
-                                               FlagSelectorStyles.None => "_No Style",
-                                               FlagSelectorStyles.ShowNone => "_Show None Value Style",
-                                               FlagSelectorStyles.ShowValueEdit => "Show _Value Editor Style",
-                                               FlagSelectorStyles.All => "_All Styles",
-                                               _ => f.ToString ()
-                                           });
-
-        return true;
-    }
-}

+ 0 - 31
Terminal.Gui/Views/FlagSelectorStyles.cs

@@ -1,31 +0,0 @@
-#nullable enable
-namespace Terminal.Gui.Views;
-
-/// <summary>
-///     Styles for <see cref="FlagSelector"/>.
-/// </summary>
-[Flags]
-public enum FlagSelectorStyles
-{
-    /// <summary>
-    ///     No styles.
-    /// </summary>
-    None = 0b_0000_0000,
-
-    /// <summary>
-    ///     Show the `None` checkbox. This will add a checkbox with the title "None" and a value of 0
-    ///     even if the flags do not contain a value of 0.
-    /// </summary>
-    ShowNone = 0b_0000_0001,
-
-    /// <summary>
-    ///     Show the value edit. This will add a read-only <see cref="TextField"/> to the <see cref="FlagSelector"/> to allow
-    ///     the user to see the value.
-    /// </summary>
-    ShowValueEdit = 0b_0000_0010,
-
-    /// <summary>
-    ///     All styles.
-    /// </summary>
-    All = ShowNone | ShowValueEdit
-}

+ 0 - 99
Terminal.Gui/Views/FlagSelectorTEnum.cs

@@ -1,99 +0,0 @@
-#nullable enable
-namespace Terminal.Gui.Views;
-
-/// <summary>
-///     Provides a user interface for displaying and selecting non-mutually-exclusive flags.
-///     Flags can be set from a dictionary or directly from an enum type.
-/// </summary>
-public sealed class FlagSelector<TEnum> : FlagSelector where TEnum : struct, Enum
-{
-    /// <summary>
-    ///     Initializes a new instance of the <see cref="FlagSelector{TEnum}"/> class.
-    /// </summary>
-    public FlagSelector ()
-    {
-        SetFlags ();
-    }
-
-    /// <summary>
-    /// Gets or sets the value of the selected flags.
-    /// </summary>
-    public new TEnum? Value
-    {
-        get => base.Value.HasValue ? (TEnum)Enum.ToObject (typeof (TEnum), base.Value.Value) : (TEnum?)null;
-        set => base.Value = value.HasValue ? Convert.ToUInt32 (value.Value) : (uint?)null;
-    }
-
-    /// <summary>
-    ///     Set the display names for the flags.
-    /// </summary>
-    /// <param name="nameSelector">A function that converts enum values to display names</param>
-    /// <remarks>
-    ///     This method allows changing the display names of the flags while keeping the flag values hard-defined by the enum type.
-    /// </remarks>
-    /// <example>
-    ///     <code>
-    ///        // Use enum values with custom display names
-    ///        var flagSelector = new FlagSelector&lt;FlagSelectorStyles&gt;();
-    ///        flagSelector.SetFlagNames(f => f switch {
-    ///             FlagSelectorStyles.ShowNone => "Show None Value",
-    ///             FlagSelectorStyles.ShowValueEdit => "Show Value Editor",
-    ///             FlagSelectorStyles.All => "Everything",
-    ///             _ => f.ToString()
-    ///        });
-    ///     </code>
-    /// </example>
-    public void SetFlagNames (Func<TEnum, string> nameSelector)
-    {
-        Dictionary<uint, string> flagsDictionary = Enum.GetValues<TEnum> ()
-                                                       .ToDictionary (f => Convert.ToUInt32 (f), nameSelector);
-        base.SetFlags (flagsDictionary);
-    }
-
-    private void SetFlags ()
-    {
-        Dictionary<uint, string> flagsDictionary = Enum.GetValues<TEnum> ()
-                                                       .ToDictionary (f => Convert.ToUInt32 (f), f => f.ToString ());
-        base.SetFlags (flagsDictionary);
-    }
-
-    /// <summary>
-    ///     Prevents calling the base SetFlags method with arbitrary flag values.
-    /// </summary>
-    /// <param name="flags"></param>
-    public override void SetFlags (IReadOnlyDictionary<uint, string> flags)
-    {
-        throw new InvalidOperationException ("Setting flag values directly is not allowed. Use SetFlagNames to change display names.");
-    }
-
-    /// <inheritdoc />
-    protected override CheckBox CreateCheckBox (string name, uint flag)
-    {
-        var checkbox = base.CreateCheckBox (name, flag);
-        checkbox.CheckedStateChanged += (sender, args) =>
-                                        {
-                                            TEnum? newValue = Value;
-
-                                            if (checkbox.CheckedState == CheckState.Checked)
-                                            {
-                                                if (flag == default!)
-                                                {
-                                                    newValue = new TEnum ();
-                                                }
-                                                else
-                                                {
-                                                    newValue = (TEnum)Enum.ToObject (typeof (TEnum), Convert.ToUInt32 (newValue) | flag);
-                                                }
-                                            }
-                                            else
-                                            {
-                                                newValue = (TEnum)Enum.ToObject (typeof (TEnum), Convert.ToUInt32 (newValue) & ~flag);
-                                            }
-
-                                            Value = newValue;
-                                        };
-
-        return checkbox;
-    }
-
-}

+ 2 - 2
Terminal.Gui/Views/Menu/MenuBarv2.cs

@@ -526,8 +526,8 @@ public class MenuBarv2 : Menuv2, IDesignable
 
         var mutuallyExclusiveOptionsSelector = new OptionSelector
         {
-            Options = ["G_ood", "_Bad", "U_gly"],
-            SelectedItem = 0
+            Labels = ["G_ood", "_Bad", "U_gly"],
+            Value = 0
         };
 
         var menuBgColorCp = new ColorPicker

+ 0 - 324
Terminal.Gui/Views/OptionSelector.cs

@@ -1,324 +0,0 @@
-#nullable enable
-using System.Diagnostics;
-
-namespace Terminal.Gui.Views;
-
-/// <summary>
-///     Provides a user interface for displaying and selecting a single item from a list of options.
-///     Each option is represented by a checkbox, but only one can be selected at a time.
-/// </summary>
-public class OptionSelector : View, IOrientation, IDesignable
-{
-    /// <summary>
-    ///     Initializes a new instance of the <see cref="OptionSelector"/> class.
-    /// </summary>
-    public OptionSelector ()
-    {
-        CanFocus = true;
-
-        Width = Dim.Auto (DimAutoStyle.Content);
-        Height = Dim.Auto (DimAutoStyle.Content);
-
-        _orientationHelper = new (this);
-        _orientationHelper.Orientation = Orientation.Vertical;
-
-        // Accept (Enter key or DoubleClick) - Raise Accept event - DO NOT advance state
-        AddCommand (Command.Accept, HandleAcceptCommand);
-
-        CreateCheckBoxes ();
-    }
-
-    private bool? HandleAcceptCommand (ICommandContext? ctx) { return RaiseAccepting (ctx); }
-
-    private int? _selectedItem;
-
-    /// <summary>
-    /// Gets or sets the index of the selected item. Will be <see langword="null"/> if no item is selected.
-    /// </summary>
-    public int? SelectedItem
-    {
-        get => _selectedItem;
-        set
-        {
-            if (value < 0 || value >= SubViews.OfType<CheckBox> ().Count ())
-            {
-                throw new ArgumentOutOfRangeException (nameof (value), @$"SelectedItem must be between 0 and {SubViews.OfType<CheckBox> ().Count ()-1}");
-
-            }
-            if (_selectedItem == value)
-            {
-                return;
-            }
-
-            int? previousSelectedItem = _selectedItem;
-            _selectedItem = value;
-
-            UpdateChecked ();
-
-            RaiseSelectedItemChanged (previousSelectedItem);
-        }
-    }
-
-    private void RaiseSelectedItemChanged (int? previousSelectedItem)
-    {
-        OnSelectedItemChanged (SelectedItem, previousSelectedItem);
-        if (SelectedItem.HasValue)
-        {
-            SelectedItemChanged?.Invoke (this, new (SelectedItem, previousSelectedItem));
-        }
-    }
-
-    /// <summary>
-    ///     Called when <see cref="SelectedItem"/> has changed.
-    /// </summary>
-    protected virtual void OnSelectedItemChanged (int? selectedItem, int? previousSelectedItem) { }
-
-    /// <summary>
-    ///     Raised when <see cref="SelectedItem"/> has changed.
-    /// </summary>
-    public event EventHandler<SelectedItemChangedArgs>? SelectedItemChanged;
-
-    private IReadOnlyList<string>? _options;
-
-    /// <summary>
-    ///     Gets or sets the list of options.
-    /// </summary>
-    public IReadOnlyList<string>? Options
-    {
-        get => _options;
-        set
-        {
-            _options = value;
-            CreateCheckBoxes ();
-        }
-    }
-
-    private bool _assignHotKeysToCheckBoxes;
-
-    /// <summary>
-    ///     If <see langword="true"/> the CheckBoxes will each be automatically assigned a hotkey.
-    ///     <see cref="UsedHotKeys"/> will be used to ensure unique keys are assigned. Set <see cref="UsedHotKeys"/>
-    ///     before setting <see cref="Options"/> with any hotkeys that may conflict with other Views.
-    /// </summary>
-    public bool AssignHotKeysToCheckBoxes
-    {
-        get => _assignHotKeysToCheckBoxes;
-        set
-        {
-            if (_assignHotKeysToCheckBoxes == value)
-            {
-                return;
-            }
-            _assignHotKeysToCheckBoxes = value;
-            CreateCheckBoxes ();
-            UpdateChecked ();
-        }
-    }
-
-    /// <summary>
-    ///     Gets the list of hotkeys already used by the CheckBoxes or that should not be used if
-    ///     <see cref="AssignHotKeysToCheckBoxes"/>
-    ///     is enabled.
-    /// </summary>
-    public List<Key> UsedHotKeys { get; } = new ();
-
-    private void CreateCheckBoxes ()
-    {
-        if (Options is null)
-        {
-            return;
-        }
-
-        foreach (CheckBox cb in RemoveAll<CheckBox> ())
-        {
-            cb.Dispose ();
-        }
-
-        for (var index = 0; index < Options.Count; index++)
-        {
-            Add (CreateCheckBox (Options [index], index));
-        }
-
-        SetLayout ();
-    }
-
-    /// <summary>
-    /// 
-    /// </summary>
-    /// <param name="name"></param>
-    /// <param name="index"></param>
-    /// <returns></returns>
-    protected virtual CheckBox CreateCheckBox (string name, int index)
-    {
-        string nameWithHotKey = name;
-        if (AssignHotKeysToCheckBoxes)
-        {
-            // Find the first char in label that is [a-z], [A-Z], or [0-9]
-            for (var i = 0; i < name.Length; i++)
-            {
-                char c = char.ToLowerInvariant (name [i]);
-                if (UsedHotKeys.Contains (new (c)) || !char.IsAsciiLetterOrDigit (c))
-                {
-                    continue;
-                }
-
-                if (char.IsAsciiLetterOrDigit (c))
-                {
-                    char? hotChar = c;
-                    nameWithHotKey = name.Insert (i, HotKeySpecifier.ToString ());
-                    UsedHotKeys.Add (new (hotChar));
-
-                    break;
-                }
-            }
-        }
-
-        var checkbox = new CheckBox
-        {
-            CanFocus = true,
-            Title = nameWithHotKey,
-            Id = name,
-            Data = index,
-            //HighlightStates = HighlightStates.Hover,
-            RadioStyle = true
-        };
-
-        checkbox.GettingAttributeForRole += (_, e) =>
-        {
-            if (SuperView is { HasFocus: false })
-            {
-                return;
-            }
-
-            switch (e.Role)
-            {
-                case VisualRole.Normal:
-                    e.Handled = true;
-
-                    if (!HasFocus)
-                    {
-                        e.Result = GetAttributeForRole (VisualRole.Focus);
-                    }
-                    else
-                    {
-                        // If _scheme was set, it's because of Hover
-                        if (checkbox.HasScheme)
-                        {
-                            e.Result = checkbox.GetAttributeForRole(VisualRole.Normal);
-                        }
-                        else
-                        {
-                            e.Result = GetAttributeForRole (VisualRole.Normal);
-                        }
-                    }
-
-                    break;
-
-                case VisualRole.HotNormal:
-                    e.Handled = true;
-
-                    if (!HasFocus)
-                    {
-                        e.Result = GetAttributeForRole (VisualRole.HotFocus);
-                    }
-                    else
-                    {
-                        e.Result = GetAttributeForRole (VisualRole.HotNormal);
-                    }
-
-                    break;
-            }
-        };
-
-        checkbox.Selecting += (sender, args) =>
-        {
-            if (RaiseSelecting (args.Context) is true)
-            {
-                args.Handled = true;
-
-                return;
-            }
-            ;
-
-            if (RaiseAccepting (args.Context) is true)
-            {
-                args.Handled = true;
-            }
-        };
-
-        checkbox.CheckedStateChanged += (sender, args) =>
-        {
-            if (checkbox.CheckedState == CheckState.Checked)
-            {
-                SelectedItem = index;
-            }
-        };
-
-        return checkbox;
-    }
-
-    private void SetLayout ()
-    {
-        foreach (View sv in SubViews)
-        {
-            if (Orientation == Orientation.Vertical)
-            {
-                sv.X = 0;
-                sv.Y = Pos.Align (Alignment.Start);
-            }
-            else
-            {
-                sv.X = Pos.Align (Alignment.Start);
-                sv.Y = 0;
-                sv.Margin!.Thickness = new (0, 0, 1, 0);
-            }
-        }
-    }
-
-    private void UpdateChecked ()
-    {
-        foreach (CheckBox cb in SubViews.OfType<CheckBox> ())
-        {
-            var index = (int)(cb.Data ?? throw new InvalidOperationException ("CheckBox.Data must be set"));
-
-            cb.CheckedState = index == SelectedItem ? CheckState.Checked : CheckState.UnChecked;
-        }
-    }
-
-    #region IOrientation
-
-    /// <summary>
-    ///     Gets or sets the <see cref="Orientation"/> for this <see cref="OptionSelector"/>. The default is
-    ///     <see cref="Orientation.Vertical"/>.
-    /// </summary>
-    public Orientation Orientation
-    {
-        get => _orientationHelper.Orientation;
-        set => _orientationHelper.Orientation = value;
-    }
-
-    private readonly OrientationHelper _orientationHelper;
-
-#pragma warning disable CS0067 // The event is never used
-    /// <inheritdoc/>
-    public event EventHandler<CancelEventArgs<Orientation>>? OrientationChanging;
-
-    /// <inheritdoc/>
-    public event EventHandler<EventArgs<Orientation>>? OrientationChanged;
-#pragma warning restore CS0067 // The event is never used
-
-    /// <summary>Called when <see cref="Orientation"/> has changed.</summary>
-    /// <param name="newOrientation"></param>
-    public void OnOrientationChanged (Orientation newOrientation) { SetLayout (); }
-
-    #endregion IOrientation
-
-    /// <inheritdoc/>
-    public bool EnableForDesign ()
-    {
-        AssignHotKeysToCheckBoxes = true;
-        Options = ["Option 1", "Option 2", "Third Option", "Option Quattro"];
-
-        return true;
-    }
-}

+ 0 - 604
Terminal.Gui/Views/RadioGroup.cs

@@ -1,604 +0,0 @@
-#nullable enable
-
-namespace Terminal.Gui.Views;
-
-/// <summary>Displays a list of mutually-exclusive items. Each items can have its own hotkey.</summary>
-public class RadioGroup : View, IDesignable, IOrientation
-{
-    /// <summary>
-    ///     Initializes a new instance of the <see cref="RadioGroup"/> class.
-    /// </summary>
-    public RadioGroup ()
-    {
-        CanFocus = true;
-
-        Width = Dim.Auto (DimAutoStyle.Content);
-        Height = Dim.Auto (DimAutoStyle.Content);
-
-        // Select (Space key or mouse click) - The default implementation sets focus. RadioGroup does not.
-        AddCommand (Command.Select, HandleSelectCommand);
-
-        // Accept (Enter key or DoubleClick) - Raise Accept event - DO NOT advance state
-        AddCommand (Command.Accept, HandleAcceptCommand);
-
-        // Hotkey - ctx may indicate a radio item hotkey was pressed. Behavior depends on HasFocus
-        //          If HasFocus and it's this.HotKey invoke Select command - DO NOT raise Accept
-        //          If it's a radio item HotKey select that item and raise Selected event - DO NOT raise Accept
-        //          If nothing is selected, select first and raise Selected event - DO NOT raise Accept
-        AddCommand (Command.HotKey, HandleHotKeyCommand);
-
-        AddCommand (Command.Up, () => HasFocus && MoveUpLeft ());
-        AddCommand (Command.Down, () => HasFocus && MoveDownRight ());
-        AddCommand (Command.Start, () => HasFocus && MoveHome ());
-        AddCommand (Command.End, () => HasFocus && MoveEnd ());
-
-        // ReSharper disable once UseObjectOrCollectionInitializer
-        _orientationHelper = new (this);
-        _orientationHelper.Orientation = Orientation.Vertical;
-
-        SetupKeyBindings ();
-
-        // By default, single click is already bound to Command.Select
-        MouseBindings.Add (MouseFlags.Button1DoubleClicked, Command.Accept);
-
-        SubViewLayout += RadioGroup_LayoutStarted;
-    }
-
-    private bool? HandleHotKeyCommand (ICommandContext? ctx)
-    {
-        // If the command did not come from a keyboard event, ignore it
-        if (ctx is not CommandContext<KeyBinding> keyCommandContext)
-        {
-            return false;
-        }
-
-        var item = keyCommandContext.Binding.Data as int?;
-
-        if (HasFocus)
-        {
-            if (item is null || HotKey == keyCommandContext.Binding.Key?.NoAlt.NoCtrl.NoShift!)
-            {
-                // It's this.HotKey OR Another View (Label?) forwarded the hotkey command to us - Act just like `Space` (Select)
-                return InvokeCommand (Command.Select);
-            }
-        }
-
-        if (item is { } && item < _radioLabels.Count)
-        {
-            if (item.Value == SelectedItem)
-            {
-                return true;
-            }
-
-            // If a RadioItem.HotKey is pressed we always set the selected item - never SetFocus
-            bool selectedItemChanged = ChangeSelectedItem (item.Value);
-
-            if (selectedItemChanged)
-            {
-                // Doesn't matter if it's handled
-                RaiseSelecting (ctx);
-
-                return true;
-            }
-
-            return false;
-        }
-
-        if (SelectedItem == -1 && ChangeSelectedItem (0))
-        {
-            if (RaiseSelecting (ctx) == true)
-            {
-                return true;
-            }
-
-            return false;
-        }
-
-        if (RaiseHandlingHotKey (ctx) == true)
-        {
-            return true;
-        }
-
-        ;
-
-        // Default Command.Hotkey sets focus
-        SetFocus ();
-
-        return true;
-    }
-
-    private bool? HandleAcceptCommand (ICommandContext? ctx)
-    {
-        if (!DoubleClickAccepts
-            && ctx is CommandContext<MouseBinding> mouseCommandContext
-            && mouseCommandContext.Binding.MouseEventArgs!.Flags.HasFlag (MouseFlags.Button1DoubleClicked))
-        {
-            return false;
-        }
-
-        return RaiseAccepting (ctx);
-    }
-
-    private bool? HandleSelectCommand (ICommandContext? ctx)
-    {
-        if (ctx is CommandContext<MouseBinding> mouseCommandContext
-            && mouseCommandContext.Binding.MouseEventArgs!.Flags.HasFlag (MouseFlags.Button1Clicked))
-        {
-            int viewportX = mouseCommandContext.Binding.MouseEventArgs.Position.X;
-            int viewportY = mouseCommandContext.Binding.MouseEventArgs.Position.Y;
-
-            int pos = Orientation == Orientation.Horizontal ? viewportX : viewportY;
-
-            int rCount = Orientation == Orientation.Horizontal
-                             ? _horizontal!.Last ().pos + _horizontal!.Last ().length
-                             : _radioLabels.Count;
-
-            if (pos < rCount)
-            {
-                int c = Orientation == Orientation.Horizontal
-                            ? _horizontal!.FindIndex (x => x.pos <= viewportX && x.pos + x.length - 2 >= viewportX)
-                            : viewportY;
-
-                if (c > -1)
-                {
-                    // Just like the user pressing the items' hotkey
-                    return InvokeCommand (Command.HotKey, new KeyBinding ([Command.HotKey], this, c)) == true;
-                }
-            }
-
-            return false;
-        }
-
-        var cursorChanged = false;
-
-        if (SelectedItem == Cursor)
-        {
-            cursorChanged = MoveDownRight ();
-
-            if (!cursorChanged)
-            {
-                cursorChanged = MoveHome ();
-            }
-        }
-
-        var selectedItemChanged = false;
-
-        if (SelectedItem != Cursor)
-        {
-            selectedItemChanged = ChangeSelectedItem (Cursor);
-        }
-
-        if (cursorChanged || selectedItemChanged)
-        {
-            if (RaiseSelecting (ctx) == true)
-            {
-                return true;
-            }
-        }
-
-        return cursorChanged || selectedItemChanged;
-    }
-
-    // TODO: Fix InvertColorsOnPress - only highlight the selected item
-
-    private void SetupKeyBindings ()
-    {
-        // Default keybindings for this view
-        if (Orientation == Orientation.Vertical)
-        {
-            KeyBindings.Remove (Key.CursorUp);
-            KeyBindings.Add (Key.CursorUp, Command.Up);
-            KeyBindings.Remove (Key.CursorDown);
-            KeyBindings.Add (Key.CursorDown, Command.Down);
-        }
-        else
-        {
-            KeyBindings.Remove (Key.CursorLeft);
-            KeyBindings.Add (Key.CursorLeft, Command.Up);
-            KeyBindings.Remove (Key.CursorRight);
-            KeyBindings.Add (Key.CursorRight, Command.Down);
-        }
-
-        KeyBindings.Remove (Key.Home);
-        KeyBindings.Add (Key.Home, Command.Start);
-        KeyBindings.Remove (Key.End);
-        KeyBindings.Add (Key.End, Command.End);
-    }
-
-    /// <summary>
-    ///     Gets or sets whether double-clicking on a Radio Item will cause the <see cref="View.Accepting"/> event to be
-    ///     raised.
-    /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         If <see langword="false"/> and Accept is not handled, the Accept event on the <see cref="View.SuperView"/> will
-    ///         be raised. The default is
-    ///         <see langword="true"/>.
-    ///     </para>
-    /// </remarks>
-    public bool DoubleClickAccepts { get; set; } = true;
-
-    private List<(int pos, int length)>? _horizontal;
-    private int _horizontalSpace = 2;
-
-    /// <summary>
-    ///     Gets or sets the horizontal space for this <see cref="RadioGroup"/> if the <see cref="Orientation"/> is
-    ///     <see cref="Orientation.Horizontal"/>
-    /// </summary>
-    public int HorizontalSpace
-    {
-        get => _horizontalSpace;
-        set
-        {
-            if (_horizontalSpace != value && Orientation == Orientation.Horizontal)
-            {
-                _horizontalSpace = value;
-                UpdateTextFormatterText ();
-                SetContentSize ();
-            }
-        }
-    }
-
-    /// <summary>
-    ///     If <see langword="true"/> the <see cref="RadioLabels"/> will each be automatically assigned a hotkey.
-    ///     <see cref="UsedHotKeys"/> will be used to ensure unique keys are assigned. Set <see cref="UsedHotKeys"/>
-    ///     before setting <see cref="RadioLabels"/> with any hotkeys that may conflict with other Views.
-    /// </summary>
-    public bool AssignHotKeysToRadioLabels { get; set; }
-
-    /// <summary>
-    ///     Gets the list of hotkeys already used by <see cref="RadioLabels"/> or that should not be used if
-    ///     <see cref="AssignHotKeysToRadioLabels"/>
-    ///     is enabled.
-    /// </summary>
-    public List<Key> UsedHotKeys { get; } = [];
-
-    private readonly List<string> _radioLabels = [];
-
-    /// <summary>
-    ///     The radio labels to display. A <see cref="Command.HotKey"/> key binding will be added for each label enabling the
-    ///     user to select
-    ///     and/or focus the radio label using the keyboard. See <see cref="View.HotKey"/> for details on how HotKeys work.
-    /// </summary>
-    /// <value>The radio labels.</value>
-    public string [] RadioLabels
-    {
-        get => _radioLabels.ToArray ();
-        set
-        {
-            // Remove old hot key bindings
-            foreach (string label in _radioLabels)
-            {
-                if (TextFormatter.FindHotKey (label, HotKeySpecifier, out _, out Key hotKey))
-                {
-                    AddKeyBindingsForHotKey (hotKey, Key.Empty);
-                }
-            }
-
-            _radioLabels.Clear ();
-
-            // Pick a unique hotkey for each radio label
-            for (var labelIndex = 0; labelIndex < value.Length; labelIndex++)
-            {
-                string name = value [labelIndex];
-                string? nameWithHotKey = name;
-
-                if (AssignHotKeysToRadioLabels)
-                {
-                    // Find the first char in label that is [a-z], [A-Z], or [0-9]
-                    for (var i = 0; i < name.Length; i++)
-                    {
-                        char c = char.ToLowerInvariant (name [i]);
-                        if (UsedHotKeys.Contains (new (c)) || !char.IsAsciiLetterOrDigit (c))
-                        {
-                            continue;
-                        }
-
-                        if (char.IsAsciiLetterOrDigit (c))
-                        {
-                            char? hotChar = c;
-                            nameWithHotKey = name.Insert (i, HotKeySpecifier.ToString ());
-                            UsedHotKeys.Add (new (hotChar));
-
-                            break;
-                        }
-                    }
-                }
-
-                _radioLabels.Add (nameWithHotKey);
-
-                if (TextFormatter.FindHotKey (nameWithHotKey, HotKeySpecifier, out _, out Key hotKey))
-                {
-                    AddKeyBindingsForHotKey (Key.Empty, hotKey, labelIndex);
-                }
-            }
-
-            SelectedItem = 0;
-            SetContentSize ();
-        }
-    }
-
-    private int _selected;
-
-    /// <summary>Gets or sets the selected radio label index.</summary>
-    /// <value>The index. -1 if no item is selected.</value>
-    public int SelectedItem
-    {
-        get => _selected;
-        set => ChangeSelectedItem (value);
-    }
-
-    /// <summary>
-    ///     INTERNAL Sets the selected item.
-    /// </summary>
-    /// <param name="value"></param>
-    /// <returns>
-    ///     <see langword="true"/> if the selected item changed.
-    /// </returns>
-    private bool ChangeSelectedItem (int value)
-    {
-        if (_selected == value || value > _radioLabels.Count - 1)
-        {
-            return false;
-        }
-
-        int savedSelected = _selected;
-        _selected = value;
-        Cursor = Math.Max (_selected, 0);
-
-        OnSelectedItemChanged (value, SelectedItem);
-        SelectedItemChanged?.Invoke (this, new (SelectedItem, savedSelected));
-
-        SetNeedsDraw ();
-
-        return true;
-    }
-
-    /// <inheritdoc/>
-    protected override bool OnDrawingContent ()
-    {
-        SetAttribute (GetAttributeForRole (VisualRole.Normal));
-
-        for (var i = 0; i < _radioLabels.Count; i++)
-        {
-            switch (Orientation)
-            {
-                case Orientation.Vertical:
-                    Move (0, i);
-
-                    break;
-                case Orientation.Horizontal:
-                    Move (_horizontal! [i].pos, 0);
-
-                    break;
-            }
-
-            string rl = _radioLabels [i];
-            SetAttribute (GetAttributeForRole (VisualRole.Normal));
-            AddStr ($"{(i == _selected ? Glyphs.Selected : Glyphs.UnSelected)} ");
-            TextFormatter.FindHotKey (rl, HotKeySpecifier, out int hotPos, out Key hotKey);
-
-            if (hotPos != -1 && hotKey != Key.Empty)
-            {
-                Rune [] rlRunes = rl.ToRunes ();
-
-                for (var j = 0; j < rlRunes.Length; j++)
-                {
-                    Rune rune = rlRunes [j];
-
-                    if (j == hotPos && i == Cursor)
-                    {
-                        SetAttribute (HasFocus ? GetAttributeForRole (VisualRole.HotFocus) : GetAttributeForRole (VisualRole.HotNormal));
-                    }
-                    else if (j == hotPos && i != Cursor)
-                    {
-                        SetAttribute (GetAttributeForRole (VisualRole.HotNormal));
-                    }
-                    else if (HasFocus && i == Cursor)
-                    {
-                        SetAttribute (GetAttributeForRole (VisualRole.Focus));
-                    }
-
-                    if (rune == HotKeySpecifier && j + 1 < rlRunes.Length)
-                    {
-                        j++;
-                        rune = rlRunes [j];
-
-                        if (i == Cursor)
-                        {
-                            SetAttribute (HasFocus ? GetAttributeForRole (VisualRole.HotFocus) : GetAttributeForRole (VisualRole.HotNormal));
-                        }
-                        else if (i != Cursor)
-                        {
-                            SetAttribute (GetAttributeForRole (VisualRole.HotNormal));
-                        }
-                    }
-
-                    Application.Driver?.AddRune (rune);
-                    SetAttribute (GetAttributeForRole (VisualRole.Normal));
-                }
-            }
-            else
-            {
-                DrawHotString (rl, HasFocus && i == Cursor);
-            }
-        }
-
-        return true;
-    }
-
-    #region IOrientation
-
-    /// <summary>
-    ///     Gets or sets the <see cref="Orientation"/> for this <see cref="RadioGroup"/>. The default is
-    ///     <see cref="Orientation.Vertical"/>.
-    /// </summary>
-    public Orientation Orientation
-    {
-        get => _orientationHelper.Orientation;
-        set => _orientationHelper.Orientation = value;
-    }
-
-    private readonly OrientationHelper _orientationHelper;
-
-#pragma warning disable CS0067 // The event is never used
-    /// <inheritdoc/>
-    public event EventHandler<CancelEventArgs<Orientation>>? OrientationChanging;
-
-    /// <inheritdoc/>
-    public event EventHandler<EventArgs<Orientation>>? OrientationChanged;
-#pragma warning restore CS0067 // The event is never used
-
-#pragma warning restore CS0067
-
-    /// <summary>Called when <see cref="Orientation"/> has changed.</summary>
-    /// <param name="newOrientation"></param>
-    public void OnOrientationChanged (Orientation newOrientation)
-    {
-        SetupKeyBindings ();
-        SetContentSize ();
-    }
-
-    #endregion IOrientation
-
-    // TODO: Add a SelectedItemChanging event like CheckBox has.
-    /// <summary>Called whenever the current selected item changes. Invokes the <see cref="SelectedItemChanged"/> event.</summary>
-    /// <param name="selectedItem"></param>
-    /// <param name="previousSelectedItem"></param>
-    protected virtual void OnSelectedItemChanged (int selectedItem, int previousSelectedItem) { }
-
-    /// <summary>
-    ///     Gets or sets the <see cref="RadioLabels"/> index for the cursor. The cursor may or may not be the selected
-    ///     RadioItem.
-    /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         Maps to either the X or Y position within <see cref="View.Viewport"/> depending on <see cref="Orientation"/>.
-    ///     </para>
-    /// </remarks>
-    public int Cursor { get; set; }
-
-    /// <inheritdoc/>
-    public override Point? PositionCursor ()
-    {
-        var x = 0;
-        var y = 0;
-
-        switch (Orientation)
-        {
-            case Orientation.Vertical:
-                y = Cursor;
-
-                break;
-            case Orientation.Horizontal:
-                if (_horizontal!.Count > 0)
-                {
-                    x = _horizontal [Cursor].pos;
-                }
-
-                break;
-
-            default:
-                return null;
-        }
-
-        Move (x, y);
-
-        return null; // Don't show the cursor
-    }
-
-    /// <summary>Raised when the selected radio label has changed.</summary>
-    public event EventHandler<SelectedItemChangedArgs>? SelectedItemChanged;
-
-    private bool MoveDownRight ()
-    {
-        if (Cursor + 1 < _radioLabels.Count)
-        {
-            Cursor++;
-            SetNeedsDraw ();
-
-            return true;
-        }
-
-        // Moving past should move focus to next view, not wrap
-        return false;
-    }
-
-    private bool MoveEnd ()
-    {
-        Cursor = Math.Max (_radioLabels.Count - 1, 0);
-
-        return true;
-    }
-
-    private bool MoveHome ()
-    {
-        if (Cursor != 0)
-        {
-            Cursor = 0;
-
-            return true;
-        }
-
-        return false;
-    }
-
-    private bool MoveUpLeft ()
-    {
-        if (Cursor > 0)
-        {
-            Cursor--;
-            SetNeedsDraw ();
-
-            return true;
-        }
-
-        // Moving past should move focus to next view, not wrap
-        return false;
-    }
-
-    private void RadioGroup_LayoutStarted (object? sender, EventArgs e) { SetContentSize (); }
-
-    private void SetContentSize ()
-    {
-        switch (Orientation)
-        {
-            case Orientation.Vertical:
-                var width = 0;
-
-                foreach (string s in _radioLabels)
-                {
-                    width = Math.Max (s.GetColumns () + 2, width);
-                }
-
-                SetContentSize (new (width, _radioLabels.Count));
-
-                break;
-
-            case Orientation.Horizontal:
-                _horizontal = new ();
-                var start = 0;
-                var length = 0;
-
-                for (var i = 0; i < _radioLabels.Count; i++)
-                {
-                    start += length;
-
-                    length = _radioLabels [i].GetColumns () + 2 + (i < _radioLabels.Count - 1 ? _horizontalSpace : 0);
-                    _horizontal.Add ((start, length));
-                }
-
-                SetContentSize (new (_horizontal.Sum (item => item.length), 1));
-
-                break;
-        }
-    }
-
-    /// <inheritdoc/>
-    public bool EnableForDesign ()
-    {
-        RadioLabels = new [] { "Option _1", "Option _2", "Option _3" };
-
-        return true;
-    }
-}

+ 244 - 0
Terminal.Gui/Views/Selectors/FlagSelector.cs

@@ -0,0 +1,244 @@
+#nullable enable
+
+using System.Collections.Immutable;
+
+namespace Terminal.Gui.Views;
+
+// DoubleClick - Focus, Select (Toggle), and Accept the item under the mouse.
+// Click - Focus, Select (Toggle), and do NOT Accept the item under the mouse.
+// Not Focused:
+//  HotKey - Restore Focus. Do NOT change Active.
+//  Item HotKey - Focus item. Select (Toggle) item. Do NOT Accept.
+// Focused:
+//  Space key - Select (Toggle) focused item. Do NOT Accept.
+//  Enter key - Select (Toggle) and Accept the focused item.
+//  HotKey - No-op.
+//  Item HotKey - Focus item, Select (Toggle), and do NOT Accept.
+
+/// <summary>
+///     Provides a user interface for displaying and selecting non-mutually-exclusive flags from a provided dictionary.
+///     <see cref="FlagSelector{TFlagsEnum}"/> provides a type-safe version where a `[Flags]` <see langword="enum"/> can be
+///     provided.
+/// </summary>
+public class FlagSelector : SelectorBase, IDesignable
+{
+    /// <inheritdoc />
+    protected override void OnSubViewAdded (View view)
+    {
+        base.OnSubViewAdded (view);
+        if (view is not CheckBox checkbox)
+        {
+            return;
+        }
+
+        checkbox.RadioStyle = false;
+
+        checkbox.CheckedStateChanging += OnCheckboxOnCheckedStateChanging;
+        checkbox.CheckedStateChanged += OnCheckboxOnCheckedStateChanged;
+        checkbox.Selecting += OnCheckboxOnSelecting;
+        checkbox.Accepting += OnCheckboxOnAccepting;
+    }
+
+    private void OnCheckboxOnCheckedStateChanging (object? sender, ResultEventArgs<CheckState> args)
+    {
+        if (sender is not CheckBox checkbox)
+        {
+            return;
+        }
+
+        if (checkbox.CheckedState == CheckState.Checked && (int)checkbox.Data! == 0 && Value == 0)
+        {
+            args.Handled = true;
+        }
+    }
+
+    private void OnCheckboxOnCheckedStateChanged (object? sender, EventArgs<CheckState> args)
+    {
+        if (sender is not CheckBox checkbox)
+        {
+            return;
+        }
+
+        int newValue = Value ?? 0;
+
+        if (checkbox.CheckedState == CheckState.Checked)
+        {
+            if ((int)checkbox.Data! == default!)
+            {
+                newValue = 0;
+            }
+            else
+            {
+                newValue |= (int)checkbox.Data!;
+            }
+        }
+        else
+        {
+            newValue &= ~(int)checkbox.Data!;
+        }
+
+        Value = newValue;
+    }
+
+    private void OnCheckboxOnSelecting (object? sender, CommandEventArgs args)
+    {
+        if (sender is not CheckBox checkbox)
+        {
+            return;
+        }
+
+        if (checkbox.CanFocus)
+        {
+            // For Select, if the view is focusable and SetFocus succeeds, by defition,
+            // the event is handled. So return what SetFocus returns.
+            checkbox.SetFocus ();
+        }
+
+        // Selecting doesn't normally propogate, so we do it here
+        if (InvokeCommand (Command.Select, args.Context) is true)
+        {
+            // Do not return here; we want to toggle the checkbox state
+            args.Handled = true;
+
+            //return;
+        }
+    }
+
+    private void OnCheckboxOnAccepting (object? sender, CommandEventArgs args)
+    {
+        if (sender is not CheckBox checkbox)
+        {
+            return;
+        }
+        Value = (int)checkbox.Data!;
+        args.Handled = false; // Do not set to false; let Accepting propagate
+    }
+
+    private int? _value;
+
+    /// <summary>
+    /// Gets or sets the value of the selected flags.
+    /// </summary>
+    public override int? Value
+    {
+        get => _value;
+        set
+        {
+            if (_updatingChecked || _value == value)
+            {
+                return;
+            }
+
+            int? previousValue = _value;
+            _value = value;
+
+            if (_value is null)
+            {
+                UncheckNone ();
+                UncheckAll ();
+            }
+            else
+            {
+                UpdateChecked ();
+            }
+
+            RaiseValueChanged (previousValue);
+        }
+    }
+
+    private void UncheckNone ()
+    {
+        // Uncheck ONLY the None checkbox (Data == 0)
+        _updatingChecked = true;
+        foreach (CheckBox cb in SubViews.OfType<CheckBox> ().Where (sv => (int)sv.Data! == 0))
+        {
+            cb.CheckedState = CheckState.UnChecked;
+        }
+        _updatingChecked = false;
+    }
+
+    private void UncheckAll ()
+    {
+        // Uncheck all NON-None checkboxes (Data != 0)
+        _updatingChecked = true;
+        foreach (CheckBox cb in SubViews.OfType<CheckBox> ().Where (sv => (int)(sv.Data ?? default!) != default!))
+        {
+            cb.CheckedState = CheckState.UnChecked;
+        }
+        _updatingChecked = false;
+    }
+
+    private bool _updatingChecked = false;
+
+    /// <inheritdoc />
+    public override void UpdateChecked ()
+    {
+        if (_updatingChecked)
+        {
+            return;
+        }
+        _updatingChecked = true;
+        foreach (CheckBox cb in SubViews.OfType<CheckBox> ())
+        {
+            var flag = (int)(cb.Data ?? throw new InvalidOperationException ("CheckBox.Data must be set"));
+
+            // If this flag is set in Value, check the checkbox. Otherwise, uncheck it.
+            if (flag == 0)
+            {
+                cb.CheckedState = (Value != 0) ? CheckState.UnChecked : CheckState.Checked;
+            }
+            else
+            {
+                cb.CheckedState = (Value & flag) == flag ? CheckState.Checked : CheckState.UnChecked;
+            }
+        }
+
+        _updatingChecked = false;
+    }
+
+    /// <inheritdoc />
+    protected override void OnCreatingSubViews ()
+    {
+        // FlagSelector supports a "None" check box; add it
+        if (Styles.HasFlag (SelectorStyles.ShowNoneFlag) && Values is { } && !Values.Contains (0))
+        {
+            Add (CreateCheckBox ("None", 0));
+        }
+    }
+
+    /// <inheritdoc />
+    protected override void OnCreatedSubViews ()
+    {
+        // If the values include 0, and ShowNoneFlag is not specified, remove the "None" check box
+        if (!Styles.HasFlag (SelectorStyles.ShowNoneFlag))
+        {
+            CheckBox? noneCheckBox = SubViews.OfType<CheckBox> ().FirstOrDefault (cb => (int)cb.Data! == 0);
+
+            if (noneCheckBox is { })
+            {
+                Remove (noneCheckBox);
+                noneCheckBox.Dispose ();
+            }
+        }
+    }
+
+    /// <inheritdoc/>
+    public bool EnableForDesign ()
+    {
+        Styles = SelectorStyles.All;
+        AssignHotKeys = true;
+        SetValuesAndLabels<SelectorStyles> ();
+        Labels = Enum.GetValues<SelectorStyles> ()
+                     .Select (
+                              l => l switch
+                                   {
+                                       SelectorStyles.None => "No Style",
+                                       SelectorStyles.ShowNoneFlag => "Show None Value Style",
+                                       SelectorStyles.ShowValue => "Show Value Editor Style",
+                                       SelectorStyles.All => "All Styles",
+                                       _ => l.ToString ()
+                                   }).ToList ();
+
+        return true;
+    }
+}

+ 46 - 0
Terminal.Gui/Views/Selectors/FlagSelectorTEnum.cs

@@ -0,0 +1,46 @@
+#nullable enable
+using System;
+
+namespace Terminal.Gui.Views;
+
+/// <summary>
+///     Provides a user interface for displaying and selecting non-mutually-exclusive flags in a type-safe way.
+///     <see cref="FlagSelector"/> provides a non-type-safe version. <c>TFlagsEnum</c> must be a valid enum type with
+///     the '[Flags]' attribute.
+/// </summary>
+public sealed class FlagSelector<TFlagsEnum> : FlagSelector where TFlagsEnum : struct, Enum
+{
+    /// <summary>
+    ///     Initializes a new instance of the <see cref="FlagSelector{TFlagsEnum}"/> class.
+    /// </summary>
+    public FlagSelector ()
+    {
+        SetValuesAndLabels<TFlagsEnum> ();
+    }
+
+    /// <summary>
+    ///     Gets or sets the value of the selected flags.
+    /// </summary>
+    public new TFlagsEnum? Value
+    {
+        get => base.Value.HasValue ? (TFlagsEnum)Enum.ToObject (typeof (TFlagsEnum), base.Value.Value) : (TFlagsEnum?)null;
+        set => base.Value = value.HasValue ? Convert.ToInt32 (value.Value) : (int?)null;
+    }
+
+    /// <summary>
+    ///     Raised when <see cref="Value"/> has changed. Provides the new value as <typeparamref name="TFlagsEnum"/>?.
+    /// </summary>
+    public new event EventHandler<EventArgs<TFlagsEnum?>>? ValueChanged;
+
+    /// <summary>
+    ///     Called when <see cref="Value"/> has changed. Raises the generic <see cref="ValueChanged"/> event.
+    /// </summary>
+    protected override void OnValueChanged (int? value, int? previousValue)
+    {
+        base.OnValueChanged (value, previousValue);
+
+        TFlagsEnum? newValue = value.HasValue ? (TFlagsEnum)Enum.ToObject (typeof (TFlagsEnum), value.Value) : null;
+
+        ValueChanged?.Invoke (this, new EventArgs<TFlagsEnum?> (newValue));
+    }
+}

+ 249 - 0
Terminal.Gui/Views/Selectors/OptionSelector.cs

@@ -0,0 +1,249 @@
+#nullable enable
+using System.Collections.Immutable;
+using System.Diagnostics;
+
+namespace Terminal.Gui.Views;
+
+// DoubleClick - Focus, Select, and Accept the item under the mouse.
+// Click - Focus, Select, and do NOT Accept the item under the mouse.
+// CanFocus - Not Focused:
+//  HotKey - Restore Focus. Advance Active. Do NOT Accept.
+//  Item HotKey - Focus item. If item is not active, make Active. Do NOT Accept.
+// !CanFocus - Not Focused:
+//  HotKey - Do NOT Restore Focus. Advance Active. Do NOT Accept.
+//  Item HotKey - Do NOT Focus item. If item is not active, make Active. Do NOT Accept.
+// Focused:
+//  Space key - If focused item is Active, move focus to and Acivate next. Else, Select current. Do NOT Accept.
+//  Enter key - Select and Accept the focused item.
+//  HotKey - Restore Focus. Advance Active. Do NOT Accept.
+//  Item HotKey - If item is not active, make Active. Do NOT Accept.
+
+/// <summary>
+///     Provides a user interface for displaying and selecting a single item from a list of options.
+///     Each option is represented by a checkbox, but only one can be selected at a time.
+///     <see cref="OptionSelector{TEnum}"/> provides a type-safe version where a <see langword="enum"/> can be
+///     provided.
+/// </summary>
+public class OptionSelector : SelectorBase, IDesignable
+{
+    /// <inheritdoc />
+    public OptionSelector ()
+    {
+        // By default, for OptionSelector, Value is set to 0. It can be set to null if a developer
+        // really wants that.
+        base.Value = 0;
+    }
+
+
+    /// <inheritdoc />
+    protected override bool OnHandlingHotKey (CommandEventArgs args)
+    {
+        if (base.OnHandlingHotKey (args) is true)
+        {
+            return true;
+        }
+        if (!CanFocus)
+        {
+            if (RaiseSelecting (args.Context) is true)
+            {
+                return true;
+            }
+        }
+        else if (!HasFocus && Value is null)
+        {
+            if (RaiseSelecting (args.Context) is true)
+            {
+                return true;
+            }
+            SetFocus ();
+            Value = Values? [0];
+            return true;
+        }
+
+        return false;
+    }
+
+    /// <inheritdoc />
+    protected override bool OnSelecting (CommandEventArgs args)
+    {
+        if (base.OnSelecting (args) is true)
+        {
+            return true;
+        }
+
+        if (!CanFocus || args.Context?.Source is not CheckBox checkBox)
+        {
+            Cycle ();
+
+            return false;
+        }
+
+        if (args.Context is CommandContext<KeyBinding> { } && (int)checkBox.Data! == Value)
+        {
+            // Caused by keypress. If the checkbox is already checked, we cycle to the next one.
+            Cycle ();
+        }
+        else
+        {
+            if (Value == (int)checkBox.Data!)
+            {
+                return true;
+            }
+
+            Value = (int)checkBox.Data!;
+
+            // if (HasFocus)
+            {
+                UpdateChecked ();
+            }
+        }
+
+        return false;
+    }
+
+
+    /// <inheritdoc />
+    protected override void OnSubViewAdded (View view)
+    {
+        base.OnSubViewAdded (view);
+        if (view is not CheckBox checkbox)
+        {
+            return;
+        }
+
+        checkbox.RadioStyle = true;
+
+        checkbox.Selecting += OnCheckboxOnSelecting;
+        checkbox.Accepting += OnCheckboxOnAccepting;
+    }
+
+
+    private void OnCheckboxOnSelecting (object? sender, CommandEventArgs args)
+    {
+        if (sender is not CheckBox checkbox)
+        {
+            return;
+        }
+
+        // Verify at most one is checked
+        Debug.Assert (SubViews.OfType<CheckBox> ().Count (cb => cb.CheckedState == CheckState.Checked) <= 1);
+
+        if (args.Context is CommandContext<MouseBinding> { } && checkbox.CheckedState == CheckState.Checked)
+        {
+            // If user clicks with mouse and item is already checked, do nothing
+            args.Handled = true;
+            return;
+        }
+
+        if (args.Context is CommandContext<KeyBinding> binding && binding.Command == Command.HotKey && checkbox.CheckedState == CheckState.Checked)
+        {
+            // If user uses an item hotkey and the item is already checked, do nothing
+            args.Handled = true;
+            return;
+        }
+
+        if (checkbox.CanFocus)
+        {
+            // For Select, if the view is focusable and SetFocus succeeds, by defition,
+            // the event is handled. So return what SetFocus returns.
+            checkbox.SetFocus ();
+        }
+
+        // Selecting doesn't normally propogate, so we do it here
+        if (InvokeCommand (Command.Select, args.Context) is true)
+        {
+            // Do not return here; we want to toggle the checkbox state
+            args.Handled = true;
+
+            return;
+        }
+
+        args.Handled = true;
+    }
+
+    private void OnCheckboxOnAccepting (object? sender, CommandEventArgs args)
+    {
+        if (sender is not CheckBox checkbox)
+        {
+            return;
+        }
+        Value = (int)checkbox.Data!;
+        args.Handled = false; // Do not set to false; let Accepting propagate
+    }
+
+    private void Cycle ()
+    {
+        int valueIndex = Values.IndexOf (v => v == Value);
+        Value = valueIndex == Values?.Count () - 1
+            ? Values! [0]
+            : Values! [valueIndex + 1];
+
+        if (HasFocus)
+        {
+            valueIndex = Values.IndexOf (v => v == Value);
+            SubViews.OfType<CheckBox> ().ToArray () [valueIndex].SetFocus ();
+        }
+
+        // Verify at most one is checked
+        Debug.Assert (SubViews.OfType<CheckBox> ().Count (cb => cb.CheckedState == CheckState.Checked) <= 1);
+    }
+
+
+    /// <summary>
+    ///     Updates the checked state of all checkbox subviews so that only the checkbox corresponding
+    ///     to the current <see cref="SelectorBase.Value"/> is checked. Throws <see cref="InvalidOperationException"/>
+    ///     if a checkbox's Data property is not set.
+    /// </summary>
+    /// <exception cref="InvalidOperationException"></exception>
+    public override void UpdateChecked ()
+    {
+        foreach (CheckBox cb in SubViews.OfType<CheckBox> ())
+        {
+            int value = (int)(cb.Data ?? throw new InvalidOperationException ("CheckBox.Data must be set"));
+
+            cb.CheckedState = value == Value ? CheckState.Checked : CheckState.UnChecked;
+        }
+
+        // Verify at most one is checked
+        Debug.Assert (SubViews.OfType<CheckBox> ().Count (cb => cb.CheckedState == CheckState.Checked) <= 1);
+    }
+
+    /// <summary>
+    ///     Gets or sets the <see cref="SelectorBase.Labels"/> index for the cursor. The cursor may or may not be the selected
+    ///     RadioItem.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Maps to either the X or Y position within <see cref="View.Viewport"/> depending on <see cref="Orientation"/>.
+    ///     </para>
+    /// </remarks>
+    public int Cursor
+    {
+        get => !CanFocus ? 0 : SubViews.OfType<CheckBox> ().ToArray ().IndexOf (Focused);
+        set
+        {
+            if (!CanFocus)
+            {
+                return;
+            }
+
+            CheckBox [] checkBoxes = SubViews.OfType<CheckBox> ().ToArray ();
+
+            if (value < 0 || value >= checkBoxes.Length)
+            {
+                throw new ArgumentOutOfRangeException (nameof (value), @"Cursor index is out of range");
+            }
+
+            checkBoxes [value].SetFocus ();
+        }
+    }
+
+    /// <inheritdoc/>
+    public bool EnableForDesign ()
+    {
+        AssignHotKeys = true;
+        Labels = ["Option 1", "Option 2", "Third Option", "Option Quattro"];
+
+        return true;
+    }
+}

+ 53 - 0
Terminal.Gui/Views/Selectors/OptionSelectorTEnum.cs

@@ -0,0 +1,53 @@
+#nullable enable
+namespace Terminal.Gui.Views;
+
+/// <summary>
+///     Provides a user interface for displaying and selecting a single item from a list of options in a type-safe way.
+///     Each option is represented by a checkbox, but only one can be selected at a time.
+///     <see cref="OptionSelector"/> provides a non-type-safe version.
+/// </summary>
+public sealed class OptionSelector<TEnum> : OptionSelector where TEnum : struct, Enum
+{
+    /// <summary>
+    ///     Initializes a new instance of the <see cref="OptionSelector{TEnum}"/> class.
+    /// </summary>
+    public OptionSelector ()
+    {
+        base.Labels = Enum.GetValues<TEnum> ().Select (f => f.ToString ()).ToArray (); ;
+    }
+
+    /// <summary>
+    ///     Gets or sets the value of the selected option.
+    /// </summary>
+    public new TEnum? Value
+    {
+        get => base.Value.HasValue ? (TEnum)Enum.ToObject (typeof (TEnum), base.Value.Value) : (TEnum?)null;
+        set => base.Value = value.HasValue ? Convert.ToInt32 (value.Value) : null;
+    }
+
+    /// <summary>
+    ///     Prevents calling the base Values property setter with arbitrary values.
+    /// </summary>
+    public override IReadOnlyList<int>? Values
+    {
+        get => base.Values;
+        set => throw new InvalidOperationException ("Setting Values directly is not allowed.");
+    }
+
+    /// <summary>
+    ///     Raised when <see cref="Value"/> has changed. Provides the new value as <typeparamref name="TEnum"/>?.
+    /// </summary>
+    public new event EventHandler<EventArgs<TEnum?>>? ValueChanged;
+
+    /// <summary>
+    ///     Called when <see cref="Value"/> has changed. Raises the generic <see cref="ValueChanged"/> event.
+    /// </summary>
+    protected override void OnValueChanged (int? value, int? previousValue)
+    {
+        base.OnValueChanged (value, previousValue);
+
+        TEnum? newValue = value.HasValue ? (TEnum)Enum.ToObject (typeof (TEnum), value.Value) : null;
+
+        ValueChanged?.Invoke (this, new (newValue));
+    }
+}

+ 509 - 0
Terminal.Gui/Views/Selectors/SelectorBase.cs

@@ -0,0 +1,509 @@
+#nullable enable
+using System.Collections.Immutable;
+
+namespace Terminal.Gui.Views;
+
+/// <summary>
+///     The abstract base class for <see cref="OptionSelector{TEnum}"/> and <see cref="FlagSelector{TFlagsEnum}"/>.
+/// </summary>
+public abstract class SelectorBase : View, IOrientation
+{
+    /// <summary>
+    ///     Initializes a new instance of the <see cref="SelectorBase"/> class.
+    /// </summary>
+    protected SelectorBase ()
+    {
+        CanFocus = true;
+
+        Width = Dim.Auto (DimAutoStyle.Content);
+        Height = Dim.Auto (DimAutoStyle.Content);
+
+        // ReSharper disable once UseObjectOrCollectionInitializer
+        _orientationHelper = new (this);
+        _orientationHelper.Orientation = Orientation.Vertical;
+
+        AddCommand (Command.Accept, HandleAcceptCommand);
+        //AddCommand (Command.HotKey, HandleHotKeyCommand);
+
+        //CreateSubViews ();
+    }
+
+    /// <inheritdoc />
+    protected override bool OnClearingViewport ()
+    {
+        //SetAttributeForRole (HasFocus ? VisualRole.Focus : VisualRole.Normal);
+        return base.OnClearingViewport ();
+    }
+
+    private SelectorStyles _styles;
+
+    /// <summary>
+    ///     Gets or sets the styles for the flag selector.
+    /// </summary>
+    public SelectorStyles Styles
+    {
+        get => _styles;
+        set
+        {
+            if (_styles == value)
+            {
+                return;
+            }
+
+            _styles = value;
+
+            CreateSubViews ();
+            UpdateChecked ();
+        }
+    }
+
+    private bool? HandleAcceptCommand (ICommandContext? ctx)
+    {
+        if (!DoubleClickAccepts
+            && ctx is CommandContext<MouseBinding> mouseCommandContext
+            && mouseCommandContext.Binding.MouseEventArgs!.Flags.HasFlag (MouseFlags.Button1DoubleClicked))
+        {
+            return false;
+        }
+
+        return RaiseAccepting (ctx);
+    }
+
+    /// <inheritdoc />
+    protected override bool OnHandlingHotKey (CommandEventArgs args)
+    {
+        // If the command did not come from a keyboard event, ignore it
+        if (args.Context is not CommandContext<KeyBinding> keyCommandContext)
+        {
+            return base.OnHandlingHotKey (args);
+        }
+
+        if ((HasFocus || !CanFocus) && HotKey == keyCommandContext.Binding.Key?.NoAlt.NoCtrl.NoShift!)
+        {
+            // It's this.HotKey OR Another View (Label?) forwarded the hotkey command to us - Act just like `Space` (Select)
+            return Focused?.InvokeCommand (Command.Select, args.Context) is true;
+        }
+        return base.OnHandlingHotKey (args);
+    }
+
+    /// <inheritdoc />
+    protected override bool OnSelecting (CommandEventArgs args)
+    {
+        return base.OnSelecting (args);
+    }
+
+    private int? _value;
+
+    /// <summary>
+    ///     Gets or sets the value of the selector. Will be <see langword="null"/> if no value is set.
+    /// </summary>
+    public virtual int? Value
+    {
+        get => _value;
+        set
+        {
+            if (value is { } && Values is { } && !Values.Contains (value ?? -1))
+            {
+                throw new ArgumentOutOfRangeException (nameof (value), @$"Value must be one of the following: {string.Join (", ", Values)}");
+            }
+
+            if (_value == value)
+            {
+                return;
+            }
+
+            int? previousValue = _value;
+            _value = value;
+
+            UpdateChecked ();
+            RaiseValueChanged (previousValue);
+        }
+    }
+
+
+    /// <summary>
+    ///     Raised the <see cref="ValueChanged"/> event.
+    /// </summary>
+    /// <param name="previousValue"></param>
+    protected void RaiseValueChanged (int? previousValue)
+    {
+        if (_valueField is { })
+        {
+            _valueField.Text = Value.ToString ();
+        }
+
+        OnValueChanged (Value, previousValue);
+
+        if (Value.HasValue)
+        {
+            ValueChanged?.Invoke (this, new (Value.Value));
+        }
+    }
+
+    /// <summary>
+    ///     Called when <see cref="Value"/> has changed.
+    /// </summary>
+    protected virtual void OnValueChanged (int? value, int? previousValue) { }
+
+    /// <summary>
+    ///     Raised when <see cref="Value"/> has changed.
+    /// </summary>
+    public event EventHandler<EventArgs<int?>>? ValueChanged;
+
+    private IReadOnlyList<int>? _values;
+
+    /// <summary>
+    ///     Gets or sets the option values. If <see cref="Values"/> is <see langword="null"/>, get will
+    ///     return values based on the <see cref="Labels"/> property.
+    /// </summary>
+    public virtual IReadOnlyList<int>? Values
+    {
+        get
+        {
+            if (_values is { })
+            {
+                return _values;
+            }
+
+            // Use Labels and assume 0..Labels.Count - 1
+            return Labels is { }
+                       ? Enumerable.Range (0, Labels.Count).ToList ()
+                       : null;
+        }
+        set
+        {
+            _values = value;
+
+            // Ensure Value defaults to the first valid entry in Values if not already set
+            if (Value is null && _values?.Any () == true)
+            {
+                Value = _values.First ();
+            }
+
+            CreateSubViews ();
+            UpdateChecked ();
+        }
+    }
+
+    private IReadOnlyList<string>? _labels;
+
+    /// <summary>
+    ///     Gets or sets the list of labels for each value in <see cref="Values"/>.
+    /// </summary>
+    public IReadOnlyList<string>? Labels
+    {
+        get => _labels;
+        set
+        {
+            _labels = value;
+
+            CreateSubViews ();
+            UpdateChecked ();
+        }
+    }
+
+    /// <summary>
+    ///     Set <see cref="Values"/> and <see cref="Labels"/> from an enum type.
+    /// </summary>
+    /// <typeparam name="TEnum">The enum type to extract from</typeparam>
+    /// <remarks>
+    ///     This is a convenience method that converts an enum to a dictionary of values and labels.
+    ///     The enum values are converted to int values and the enum names become the labels.
+    /// </remarks>
+    public void SetValuesAndLabels<TEnum> () where TEnum : struct, Enum
+    {
+        IEnumerable<int> values = Enum.GetValues<TEnum> ().Select (f => Convert.ToInt32 (f));
+        Values = values.ToImmutableList ().AsReadOnly ();
+        Labels = Enum.GetNames<TEnum> ();
+    }
+
+    private bool _assignHotKeys;
+
+    /// <summary>
+    ///     If <see langword="true"/> each label will automatically be assigned a unique hotkey.
+    ///     <see cref="UsedHotKeys"/> will be used to ensure unique keys are assigned. Set <see cref="UsedHotKeys"/>
+    ///     before setting <see cref="Labels"/> with any hotkeys that may conflict with other Views.
+    /// </summary>
+    public bool AssignHotKeys
+    {
+        get => _assignHotKeys;
+        set
+        {
+            if (_assignHotKeys == value)
+            {
+                return;
+            }
+
+            _assignHotKeys = value;
+
+            CreateSubViews ();
+            UpdateChecked ();
+        }
+    }
+
+    /// <summary>
+    ///     Gets or sets the set of hotkeys that are already used by labels or should not be used when
+    ///     <see cref="AssignHotKeys"/> is enabled.
+    ///     <para>
+    ///         This property is used to ensure that automatically assigned hotkeys do not conflict with
+    ///         hotkeys used elsewhere in the application. Set <see cref="UsedHotKeys"/> before setting
+    ///         <see cref="Labels"/> if there are hotkeys that may conflict with other views.
+    ///     </para>
+    /// </summary>
+    public HashSet<Key> UsedHotKeys { get; set; } = [];
+
+    private TextField? _valueField;
+
+    /// <summary>
+    ///     Creates the subviews for this selector.
+    /// </summary>
+    public void CreateSubViews ()
+    {
+        foreach (View sv in RemoveAll ())
+        {
+            if (AssignHotKeys)
+            {
+                UsedHotKeys.Remove (sv.HotKey);
+            }
+
+            sv.Dispose ();
+        }
+
+        if (Labels is null)
+        {
+            return;
+        }
+
+        if (Labels?.Count != Values?.Count)
+        {
+            return;
+        }
+
+        OnCreatingSubViews ();
+
+        for (var index = 0; index < Labels?.Count; index++)
+        {
+            Add (CreateCheckBox (Labels.ElementAt (index), Values!.ElementAt (index)));
+        }
+
+        if (Styles.HasFlag (SelectorStyles.ShowValue))
+        {
+            _valueField = new ()
+            {
+                Id = "valueField",
+                Text = Value.ToString (),
+
+                // TODO: Don't hardcode this; base it on max Value
+                Width = 5,
+                ReadOnly = true
+            };
+
+            Add (_valueField);
+        }
+
+        OnCreatedSubViews ();
+
+        AssignUniqueHotKeys ();
+        SetLayout ();
+    }
+
+    /// <summary>
+    ///     Called before <see cref="CreateSubViews"/> creates the default subviews (Checkboxes and ValueField).
+    /// </summary>
+    protected virtual void OnCreatingSubViews () { }
+
+    /// <summary>
+    ///     Called after <see cref="CreateSubViews"/> creates the default subviews (Checkboxes and ValueField).
+    /// </summary>
+    protected virtual void OnCreatedSubViews () { }
+
+    /// <summary>
+    ///     INTERNAL: Creates a checkbox subview
+    /// </summary>
+    protected CheckBox CreateCheckBox (string label, int value)
+    {
+        var checkbox = new CheckBox
+        {
+            CanFocus = true,
+            Title = label,
+            Id = label,
+            Data = value,
+            HighlightStates = MouseState.In,
+        };
+
+        return checkbox;
+    }
+
+    /// <summary>
+    ///     Assigns unique hotkeys to the labels of the subviews created by <see cref="CreateSubViews"/>.
+    /// </summary>
+    private void AssignUniqueHotKeys ()
+    {
+        if (!AssignHotKeys || Labels is null)
+        {
+            return;
+        }
+
+        foreach (View subView in SubViews)
+        {
+            string label = subView.Title ?? string.Empty;
+
+            // Check if there's already a hotkey defined
+            if (TextFormatter.FindHotKey (label, HotKeySpecifier, out int hotKeyPos, out Key existingHotKey))
+            {
+                // Label already has a hotkey - preserve it if available
+                if (!UsedHotKeys.Contains (existingHotKey))
+                {
+                    subView.HotKey = existingHotKey;
+                    UsedHotKeys.Add (existingHotKey);
+                    continue; // Keep existing hotkey specifier in label
+                }
+                else
+                {
+                    // Existing hotkey is already used, remove it and assign new one
+                    label = TextFormatter.RemoveHotKeySpecifier (label, hotKeyPos, HotKeySpecifier);
+                }
+            }
+
+            // Assign a new hotkey
+            Rune [] runes = label.EnumerateRunes ().ToArray ();
+
+            for (var i = 0; i < runes.Count (); i++)
+            {
+                Rune lower = Rune.ToLowerInvariant (runes [i]);
+                var newKey = new Key (lower.Value);
+
+                if (UsedHotKeys.Contains (newKey))
+                {
+                    continue;
+                }
+
+                if (!newKey.IsValid || newKey == Key.Empty || newKey == Key.Space || Rune.IsControl (newKey.AsRune))
+                {
+                    continue;
+                }
+
+                subView.Title = label.Insert (i, HotKeySpecifier.ToString ());
+                subView.HotKey = newKey;
+                UsedHotKeys.Add (subView.HotKey);
+
+                break;
+            }
+        }
+    }
+
+    private int _horizontalSpace = 2;
+
+    /// <summary>
+    ///     Gets or sets the horizontal space for this <see cref="OptionSelector"/> if the <see cref="Orientation"/> is
+    ///     <see cref="Orientation.Horizontal"/>
+    /// </summary>
+    public int HorizontalSpace
+    {
+        get => _horizontalSpace;
+        set
+        {
+            if (_horizontalSpace != value)
+            {
+                _horizontalSpace = value;
+                SetLayout ();
+                // Pos.Align requires extra layout; good practice to call
+                // Layout to ensure Pos.Align gets updated
+                // TODO: See https://github.com/gui-cs/Terminal.Gui/issues/3951 which, if fixed, will 
+                // TODO: negate need for this hack
+                Layout ();
+            }
+        }
+    }
+
+    private void SetLayout ()
+    {
+        int maxNaturalCheckBoxWidth = 0;
+        if (Values?.Count > 0 && Orientation == Orientation.Vertical)
+        {
+            // TODO: See https://github.com/gui-cs/Terminal.Gui/issues/3951 which, if fixed, will 
+            // TODO: negate need for this hack
+            maxNaturalCheckBoxWidth = SubViews.OfType<CheckBox> ().Max (
+                                                             v =>
+                                                             {
+                                                                 v.SetRelativeLayout (Application.Screen.Size);
+                                                                 v.Layout ();
+                                                                 return v.Frame.Width;
+                                                             });
+        }
+
+        for (var i = 0; i < SubViews.Count; i++)
+        {
+            if (Orientation == Orientation.Vertical)
+            {
+                SubViews.ElementAt (i).X = 0;
+                SubViews.ElementAt (i).Y = Pos.Align (Alignment.Start, AlignmentModes.StartToEnd);
+                SubViews.ElementAt (i).Margin!.Thickness = new (0);
+                SubViews.ElementAt (i).Width = Dim.Func (_ => Math.Max (Viewport.Width, maxNaturalCheckBoxWidth));
+            }
+            else
+            {
+                SubViews.ElementAt (i).X = Pos.Align (Alignment.Start, AlignmentModes.StartToEnd);
+                SubViews.ElementAt (i).Y = 0;
+                SubViews.ElementAt (i).Margin!.Thickness = new (0, 0, (i < SubViews.Count - 1) ? _horizontalSpace : 0, 0);
+                SubViews.ElementAt (i).Width = Dim.Auto ();
+            }
+        }
+    }
+
+    /// <summary>
+    ///     Called when the checked state of the checkboxes needs to be updated.
+    /// </summary>
+    /// <exception cref="InvalidOperationException"></exception>
+    public abstract void UpdateChecked ();
+
+
+    /// <summary>
+    ///     Gets or sets whether double-clicking on an Item will cause the <see cref="View.Accepting"/> event to be
+    ///     raised.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         If <see langword="false"/> and Accept is not handled, the Accept event on the <see cref="View.SuperView"/> will
+    ///         be raised. The default is
+    ///         <see langword="true"/>.
+    ///     </para>
+    /// </remarks>
+    public bool DoubleClickAccepts { get; set; } = true;
+
+    #region IOrientation
+
+    /// <summary>
+    ///     Gets or sets the <see cref="Orientation"/> for this <see cref="SelectorBase"/>. The default is
+    ///     <see cref="Orientation.Vertical"/>.
+    /// </summary>
+    public Orientation Orientation
+    {
+        get => _orientationHelper.Orientation;
+        set => _orientationHelper.Orientation = value;
+    }
+
+    private readonly OrientationHelper _orientationHelper;
+
+#pragma warning disable CS0067 // The event is never used
+    /// <inheritdoc/>
+    public event EventHandler<CancelEventArgs<Orientation>>? OrientationChanging;
+
+    /// <inheritdoc/>
+    public event EventHandler<EventArgs<Orientation>>? OrientationChanged;
+#pragma warning restore CS0067 // The event is never used
+
+    /// <summary>Called when <see cref="Orientation"/> has changed.</summary>
+    /// <param name="newOrientation"></param>
+    public void OnOrientationChanged (Orientation newOrientation)
+    {
+        SetLayout ();
+        // Pos.Align requires extra layout; good practice to call
+        // Layout to ensure Pos.Align gets updated
+        // TODO: See https://github.com/gui-cs/Terminal.Gui/issues/3951 which, if fixed, will
+        // TODO: negate need for this hack
+        Layout ();
+    }
+
+    #endregion IOrientation
+}

+ 42 - 0
Terminal.Gui/Views/Selectors/SelectorStyles.cs

@@ -0,0 +1,42 @@
+#nullable enable
+namespace Terminal.Gui.Views;
+
+/// <summary>
+///     Styles for <see cref="FlagSelector{TFlagsEnum}"/> and <see cref="OptionSelector{TEnum}"/>.
+/// </summary>
+[Flags]
+public enum SelectorStyles
+{
+    /// <summary>
+    ///     No styles.
+    /// </summary>
+    None = 0b_0000_0000,
+
+    /// <summary>
+    ///     Show the `None` checkbox. This will add a checkbox with the title "None" that when checked will cause the value to
+    ///     be set to 0.
+    ///     The `None` checkbox will be added even if the flags do not contain a value of 0.
+    ///     Valid only for <see cref="FlagSelector"/> and <see cref="FlagSelector{TFlagsEnum}"/>
+    /// </summary>
+    ShowNoneFlag = 0b_0000_0001,
+
+    // TODO: Implement this.
+    /// <summary>
+    ///     Show the `All` checkbox.  This will add a checkbox with the title "All" that when checked will
+    ///     cause all flags to be set. Unchecking the "All" checkbox will set the value to 0.
+    ///     Valid only for <see cref="FlagSelector"/> and <see cref="FlagSelector{TFlagsEnum}"/>
+    /// </summary>
+    ShowAllFlag = 0b_0000_0010,
+
+    // TODO: Make the TextField a TextValidateField so it can be editable and validate the value.
+    /// <summary>
+    ///     Show the value field. This will add a read-only <see cref="TextField"/> to the <see cref="FlagSelector"/> to allow
+    ///     the user to see the value.
+    /// </summary>
+    ShowValue = 0b_0000_0100,
+
+    /// <summary>
+    ///     All styles.
+    /// </summary>
+    All = ShowNoneFlag | ShowAllFlag | ShowValue
+}

+ 4 - 0
Terminal.sln

@@ -163,6 +163,10 @@ Global
 		{8C643A64-2A77-4432-987A-2E72BD9708E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{8C643A64-2A77-4432-987A-2E72BD9708E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{8C643A64-2A77-4432-987A-2E72BD9708E3}.Release|Any CPU.Build.0 = Release|Any CPU
+		{566AFB59-FF8C-FFF4-C1F4-049B6246E4A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{566AFB59-FF8C-FFF4-C1F4-049B6246E4A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{566AFB59-FF8C-FFF4-C1F4-049B6246E4A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{566AFB59-FF8C-FFF4-C1F4-049B6246E4A7}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 22 - 22
Tests/IntegrationTests/UICatalog/ScenarioTests.cs

@@ -273,19 +273,19 @@ public class ScenarioTests : TestsAllViews
 
         var label = new Label { X = 0, Y = 0, Text = "x:" };
         locationFrame.Add (label);
-        RadioGroup xRadioGroup = new () { X = 0, Y = Pos.Bottom (label), RadioLabels = radioItems };
+        OptionSelector xOptionSelector = new () { X = 0, Y = Pos.Bottom (label), Labels = radioItems };
         TextField xText = new () { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{xVal}" };
         locationFrame.Add (xText);
 
-        locationFrame.Add (xRadioGroup);
+        locationFrame.Add (xOptionSelector);
 
         radioItems = new [] { "Percent(y)", "AnchorEnd(y)", "Center", "Absolute(y)" };
-        label = new () { X = Pos.Right (xRadioGroup) + 1, Y = 0, Text = "y:" };
+        label = new () { X = Pos.Right (xOptionSelector) + 1, Y = 0, Text = "y:" };
         locationFrame.Add (label);
         TextField yText = new () { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{yVal}" };
         locationFrame.Add (yText);
-        RadioGroup yRadioGroup = new () { X = Pos.X (label), Y = Pos.Bottom (label), RadioLabels = radioItems };
-        locationFrame.Add (yRadioGroup);
+        OptionSelector yOptionSelector = new () { X = Pos.X (label), Y = Pos.Bottom (label), Labels = radioItems };
+        locationFrame.Add (yOptionSelector);
 
         FrameView sizeFrame = new ()
         {
@@ -299,19 +299,19 @@ public class ScenarioTests : TestsAllViews
         radioItems = new [] { "Auto()", "Percent(width)", "Fill(width)", "Absolute(width)" };
         label = new () { X = 0, Y = 0, Text = "width:" };
         sizeFrame.Add (label);
-        RadioGroup wRadioGroup = new () { X = 0, Y = Pos.Bottom (label), RadioLabels = radioItems };
+        OptionSelector wOptionSelector = new () { X = 0, Y = Pos.Bottom (label), Labels = radioItems };
         TextField wText = new () { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{wVal}" };
         sizeFrame.Add (wText);
-        sizeFrame.Add (wRadioGroup);
+        sizeFrame.Add (wOptionSelector);
 
         radioItems = new [] { "Auto()", "Percent(height)", "Fill(height)", "Absolute(height)" };
-        label = new () { X = Pos.Right (wRadioGroup) + 1, Y = 0, Text = "height:" };
+        label = new () { X = Pos.Right (wOptionSelector) + 1, Y = 0, Text = "height:" };
         sizeFrame.Add (label);
         TextField hText = new () { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{hVal}" };
         sizeFrame.Add (hText);
 
-        RadioGroup hRadioGroup = new () { X = Pos.X (label), Y = Pos.Bottom (label), RadioLabels = radioItems };
-        sizeFrame.Add (hRadioGroup);
+        OptionSelector hOptionSelector = new () { X = Pos.X (label), Y = Pos.Bottom (label), Labels = radioItems };
+        sizeFrame.Add (hOptionSelector);
 
         settingsPane.Add (sizeFrame);
 
@@ -341,7 +341,7 @@ public class ScenarioTests : TestsAllViews
                                                  curView = CreateClass (viewClasses.Values.ToArray () [classListView.SelectedItem]);
                                              };
 
-        xRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (curView);
+        xOptionSelector.ValueChanged += (_, _) => DimPosChanged (curView);
 
         xText.TextChanged += (s, args) =>
                              {
@@ -365,9 +365,9 @@ public class ScenarioTests : TestsAllViews
                                  { }
                              };
 
-        yRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (curView);
+        yOptionSelector.ValueChanged += (_, _) => DimPosChanged (curView);
 
-        wRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (curView);
+        wOptionSelector.ValueChanged += (_, _) => DimPosChanged (curView);
 
         wText.TextChanged += (s, args) =>
                              {
@@ -391,7 +391,7 @@ public class ScenarioTests : TestsAllViews
                                  { }
                              };
 
-        hRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (curView);
+        hOptionSelector.ValueChanged += (_, _) => DimPosChanged (curView);
 
         top.Add (leftPane, settingsPane, hostPane);
 
@@ -443,7 +443,7 @@ public class ScenarioTests : TestsAllViews
 
             try
             {
-                switch (xRadioGroup.SelectedItem)
+                switch (xOptionSelector.Value)
                 {
                     case 0:
                         view.X = Pos.Percent (xVal);
@@ -463,7 +463,7 @@ public class ScenarioTests : TestsAllViews
                         break;
                 }
 
-                switch (yRadioGroup.SelectedItem)
+                switch (yOptionSelector.Value)
                 {
                     case 0:
                         view.Y = Pos.Percent (yVal);
@@ -483,7 +483,7 @@ public class ScenarioTests : TestsAllViews
                         break;
                 }
 
-                switch (wRadioGroup.SelectedItem)
+                switch (wOptionSelector.Value)
                 {
                     case 0:
                         view.Width = Dim.Percent (wVal);
@@ -499,7 +499,7 @@ public class ScenarioTests : TestsAllViews
                         break;
                 }
 
-                switch (hRadioGroup.SelectedItem)
+                switch (hOptionSelector.Value)
                 {
                     case 0:
                         view.Height = Dim.Percent (hVal);
@@ -530,8 +530,8 @@ public class ScenarioTests : TestsAllViews
 
             try
             {
-                xRadioGroup.SelectedItem = posNames.IndexOf (posNames.First (s => x.Contains (s)));
-                yRadioGroup.SelectedItem = posNames.IndexOf (posNames.First (s => y.Contains (s)));
+                xOptionSelector.Value = posNames.IndexOf (posNames.First (s => x.Contains (s)));
+                yOptionSelector.Value = posNames.IndexOf (posNames.First (s => y.Contains (s)));
             }
             catch (InvalidOperationException e)
             {
@@ -545,8 +545,8 @@ public class ScenarioTests : TestsAllViews
             var w = view.Width!.ToString ();
             var h = view.Height!.ToString ();
 
-            wRadioGroup.SelectedItem = dimNames.IndexOf (dimNames.First (s => w.Contains (s)));
-            hRadioGroup.SelectedItem = dimNames.IndexOf (dimNames.First (s => h.Contains (s)));
+            wOptionSelector.Value = dimNames.IndexOf (dimNames.First (s => w.Contains (s)));
+            hOptionSelector.Value = dimNames.IndexOf (dimNames.First (s => h.Contains (s)));
 
             wText.Text = $"{view.Frame.Width}";
             hText.Text = $"{view.Frame.Height}";

+ 2 - 1
Tests/TerminalGuiFluentTesting/GuiTestContext.cs

@@ -1,4 +1,5 @@
-using System.Diagnostics;
+using System.Collections.Concurrent;
+using System.Diagnostics;
 using System.Drawing;
 using System.Text;
 using Microsoft.Extensions.Logging;

+ 2 - 6
Tests/UnitTests/View/Layout/Pos.CombineTests.cs

@@ -32,8 +32,6 @@ public class PosCombineTests (ITestOutputHelper output)
 
         Assert.Throws<LayoutException> (() => Application.Run (t));
         t.Dispose ();
-        Application.Shutdown ();
-
         v2.Dispose ();
     }
 
@@ -73,8 +71,6 @@ public class PosCombineTests (ITestOutputHelper output)
         var foundView = View.GetViewsUnderLocation (new Point(9, 4), ViewportSettingsFlags.None).LastOrDefault ();
         Assert.Equal (foundView, view2);
         Application.Top.Dispose ();
-        Application.ResetState (ignoreDisposed: true);
-
     }
 
     [Fact]
@@ -113,8 +109,8 @@ public class PosCombineTests (ITestOutputHelper output)
         Application.StopAfterFirstIteration = true;
 
         Assert.Throws<LayoutException> (() => Application.Run ());
+        Application.Top.Dispose ();
         top.Dispose ();
-        Application.ResetState (ignoreDisposed: true);
+        Application.Shutdown ();
     }
-
 }

+ 4 - 4
Tests/UnitTests/Views/CheckBoxTests.cs

@@ -321,24 +321,24 @@ public class CheckBoxTests (ITestOutputHelper output)
     [InlineData (CheckState.Checked)]
     [InlineData (CheckState.UnChecked)]
     [InlineData (CheckState.None)]
-    public void Selected_Handle_Event_Does_Not_Prevent_Change (CheckState initialState)
+    public void Activated_Handle_Event_Prevents_Change (CheckState initialState)
     {
         var ckb = new CheckBox { AllowCheckStateNone = true };
         var checkedInvoked = false;
 
         ckb.CheckedState = initialState;
 
-        ckb.Selecting += OnSelecting;
+        ckb.Selecting += OnActivating;
 
         Assert.Equal (initialState, ckb.CheckedState);
         bool? ret = ckb.InvokeCommand (Command.Select);
         Assert.True (ret);
         Assert.True (checkedInvoked);
-        Assert.NotEqual (initialState, ckb.CheckedState);
+        Assert.Equal (initialState, ckb.CheckedState);
 
         return;
 
-        void OnSelecting (object sender, CommandEventArgs e)
+        void OnActivating (object sender, CommandEventArgs e)
         {
             checkedInvoked = true;
             e.Handled = true;

+ 0 - 775
Tests/UnitTests/Views/RadioGroupTests.cs

@@ -1,775 +0,0 @@
-using UnitTests;
-using Xunit.Abstractions;
-
-// ReSharper disable AccessToModifiedClosure
-
-namespace UnitTests.ViewsTests;
-
-public class RadioGroupTests (ITestOutputHelper output)
-{
-    [Fact]
-    public void Constructors_Defaults ()
-    {
-        var rg = new RadioGroup ();
-        Assert.True (rg.CanFocus);
-        Assert.Empty (rg.RadioLabels);
-        Assert.Equal (Rectangle.Empty, rg.Frame);
-        Assert.Equal (0, rg.SelectedItem);
-
-        rg = new () { RadioLabels = new [] { "Test" } };
-        Assert.True (rg.CanFocus);
-        Assert.Single (rg.RadioLabels);
-        Assert.Equal (0, rg.SelectedItem);
-
-        rg = new ()
-        {
-            X = 1,
-            Y = 2,
-            Width = 20,
-            Height = 5,
-            RadioLabels = new [] { "Test" }
-        };
-        Assert.True (rg.CanFocus);
-        Assert.Single (rg.RadioLabels);
-        Assert.Equal (new (1, 2, 20, 5), rg.Frame);
-        Assert.Equal (0, rg.SelectedItem);
-
-        rg = new () { X = 1, Y = 2, RadioLabels = new [] { "Test" } };
-
-        var view = new View { Width = 30, Height = 40 };
-        view.Add (rg);
-        view.BeginInit ();
-        view.EndInit ();
-        view.LayoutSubViews ();
-
-        Assert.True (rg.CanFocus);
-        Assert.Single (rg.RadioLabels);
-        Assert.Equal (new (1, 2, 6, 1), rg.Frame);
-        Assert.Equal (0, rg.SelectedItem);
-    }
-
-    [Fact]
-    public void Initialize_SelectedItem_With_Minus_One ()
-    {
-        var rg = new RadioGroup { RadioLabels = new [] { "Test" }, SelectedItem = -1 };
-        Application.Top = new ();
-        Application.Top.Add (rg);
-        rg.SetFocus ();
-
-        Assert.Equal (-1, rg.SelectedItem);
-        Application.RaiseKeyDownEvent (Key.Space);
-        Assert.Equal (0, rg.SelectedItem);
-
-        Application.Top.Dispose ();
-    }
-
-    [Fact]
-    public void HotKeyBindings_Are_Added_Correctly ()
-    {
-        var rg = new RadioGroup { RadioLabels = new [] { "_Left", "_Right" } };
-        Assert.NotEmpty (rg.HotKeyBindings.GetCommands (Key.L));
-        Assert.NotEmpty (rg.HotKeyBindings.GetCommands (Key.R));
-
-        Assert.NotEmpty (rg.HotKeyBindings.GetCommands (Key.L.WithShift));
-        Assert.NotEmpty (rg.HotKeyBindings.GetCommands (Key.L.WithAlt));
-
-        Assert.NotEmpty (rg.HotKeyBindings.GetCommands (Key.R.WithShift));
-        Assert.NotEmpty (rg.HotKeyBindings.GetCommands (Key.R.WithAlt));
-    }
-
-    [Fact]
-    public void Commands_HasFocus ()
-    {
-        Application.Navigation = new ();
-        var rg = new RadioGroup
-        {
-            Id = "rg",
-            RadioLabels = ["Test", "New Test"]
-        };
-        Application.Top = new ();
-        Application.Top.Add (rg);
-        rg.SetFocus ();
-        Assert.Equal (Orientation.Vertical, rg.Orientation);
-
-        var selectedItemChangedCount = 0;
-        rg.SelectedItemChanged += (s, e) => selectedItemChangedCount++;
-
-        var selectingCount = 0;
-        rg.Selecting += (s, e) => selectingCount++;
-
-        var acceptedCount = 0;
-        rg.Accepting += (s, e) => acceptedCount++;
-
-        // By default the first item is selected
-        Assert.Equal (0, rg.SelectedItem);
-        Assert.Equal (0, selectedItemChangedCount);
-        Assert.Equal (0, selectingCount);
-        Assert.Equal (0, acceptedCount);
-        Assert.Equal (Key.Empty, rg.HotKey);
-
-        // With HasFocus
-        // Test up/down without Select
-        Assert.False (Application.RaiseKeyDownEvent (Key.CursorUp)); // Should not change (should focus prev view if there was one, which there isn't)
-        Assert.Equal (0, rg.SelectedItem);
-        Assert.Equal (0, rg.Cursor);
-        Assert.Equal (0, selectedItemChangedCount);
-        Assert.Equal (0, selectingCount);
-        Assert.Equal (0, acceptedCount);
-
-        Assert.True (Application.RaiseKeyDownEvent (Key.CursorDown));
-        Assert.Equal (0, rg.SelectedItem); // Cursor changed, but selection didnt
-        Assert.Equal (1, rg.Cursor);
-        Assert.Equal (0, selectedItemChangedCount);
-        Assert.Equal (0, selectingCount);
-        Assert.Equal (0, acceptedCount);
-
-        Assert.False (Application.RaiseKeyDownEvent (Key.CursorDown)); // Should not change selection (should focus next view if there was one, which there isn't)
-        Assert.Equal (0, rg.SelectedItem);
-        Assert.Equal (1, rg.Cursor);
-        Assert.Equal (0, selectedItemChangedCount);
-        Assert.Equal (0, selectingCount);
-        Assert.Equal (0, acceptedCount);
-
-        // Test Select (Space) when Cursor != SelectedItem - Should select cursor
-        Assert.True (Application.RaiseKeyDownEvent (Key.Space));
-        Assert.Equal (1, rg.SelectedItem);
-        Assert.Equal (1, rg.Cursor);
-        Assert.Equal (1, selectedItemChangedCount);
-        Assert.Equal (1, selectingCount);
-        Assert.Equal (0, acceptedCount);
-
-        // Test Select (Space) when Cursor == SelectedItem - Should cycle
-        Assert.True (Application.RaiseKeyDownEvent (Key.Space));
-        Assert.Equal (0, rg.SelectedItem);
-        Assert.Equal (0, rg.Cursor);
-        Assert.Equal (2, selectedItemChangedCount);
-        Assert.Equal (2, selectingCount);
-        Assert.Equal (0, acceptedCount);
-
-        Assert.True (Application.RaiseKeyDownEvent (Key.Space));
-        Assert.Equal (1, rg.SelectedItem);
-        Assert.Equal (1, rg.Cursor);
-        Assert.True (Application.RaiseKeyDownEvent (Key.Space));
-        Assert.Equal (0, rg.SelectedItem);
-        Assert.Equal (0, rg.Cursor);
-        Assert.True (Application.RaiseKeyDownEvent (Key.Space));
-        Assert.Equal (1, rg.SelectedItem);
-        Assert.Equal (1, rg.Cursor);
-
-        Assert.True (Application.RaiseKeyDownEvent (Key.Home));
-        Assert.Equal (1, rg.SelectedItem);
-        Assert.Equal (0, rg.Cursor);
-        Assert.True (Application.RaiseKeyDownEvent (Key.Space));
-        Assert.Equal (0, rg.SelectedItem);
-        Assert.Equal (0, rg.Cursor);
-
-        Assert.True (Application.RaiseKeyDownEvent (Key.End));
-        Assert.Equal (0, rg.SelectedItem);
-        Assert.Equal (1, rg.Cursor);
-        Assert.True (Application.RaiseKeyDownEvent (Key.Space));
-        Assert.Equal (1, rg.SelectedItem);
-        Assert.Equal (1, rg.Cursor);
-        Assert.Equal (7, selectedItemChangedCount);
-        Assert.Equal (7, selectingCount);
-        Assert.Equal (0, acceptedCount);
-
-        // Test HotKey
-        //    Selected == Cursor (1) - Advance state and raise Select event - DO NOT raise Accept
-
-        rg.HotKey = Key.L;
-        Assert.Equal (Key.L, rg.HotKey);
-        Assert.True (Application.RaiseKeyDownEvent (rg.HotKey));
-        Assert.Equal (0, rg.SelectedItem);
-        Assert.Equal (0, rg.Cursor);
-        Assert.Equal (8, selectedItemChangedCount);
-        Assert.Equal (8, selectingCount);
-        Assert.Equal (0, acceptedCount);
-
-        //     Make Selected != Cursor
-        Assert.True (Application.RaiseKeyDownEvent (Key.CursorDown));
-        Assert.Equal (0, rg.SelectedItem);
-        Assert.Equal (1, rg.Cursor);
-
-        //    Selected != Cursor - Raise HotKey event - Since we're focused, this should just advance
-        Assert.True (Application.RaiseKeyDownEvent (rg.HotKey));
-        Assert.Equal (1, rg.SelectedItem);
-        Assert.Equal (1, rg.Cursor);
-        Assert.Equal (9, selectedItemChangedCount);
-        Assert.Equal (9, selectingCount);
-        Assert.Equal (0, acceptedCount);
-
-        Application.ResetState (true);
-    }
-
-    [Fact]
-    public void HotKey_HasFocus_False ()
-    {
-        Application.Navigation = new ();
-        var rg = new RadioGroup { RadioLabels = ["Test", "New Test"] };
-        Application.Top = new ();
-
-        // With !HasFocus
-        View otherView = new () { Id = "otherView", CanFocus = true };
-
-        Label label = new ()
-        {
-            Id = "label",
-            Title = "_R"
-        };
-
-        Application.Top.Add (label, rg, otherView);
-        otherView.SetFocus ();
-
-        var selectedItemChangedCount = 0;
-        rg.SelectedItemChanged += (s, e) => selectedItemChangedCount++;
-
-        var selectCount = 0;
-        rg.Selecting += (s, e) => selectCount++;
-
-        var acceptCount = 0;
-        rg.Accepting += (s, e) => acceptCount++;
-
-        // By default the first item is selected
-        Assert.Equal (0, rg.SelectedItem);
-        Assert.Equal (Orientation.Vertical, rg.Orientation);
-        Assert.Equal (0, selectedItemChangedCount);
-        Assert.Equal (0, selectCount);
-        Assert.Equal (0, acceptCount);
-        Assert.Equal (Key.Empty, rg.HotKey);
-
-        Assert.False (rg.HasFocus);
-
-        // Test HotKey
-        //    Selected (0) == Cursor (0) - SetFocus
-        rg.HotKey = Key.L;
-        Assert.Equal (Key.L, rg.HotKey);
-        Assert.True (Application.RaiseKeyDownEvent (rg.HotKey));
-        Assert.True (rg.HasFocus);
-        Assert.Equal (0, rg.SelectedItem);
-        Assert.Equal (0, rg.Cursor);
-        Assert.Equal (0, selectedItemChangedCount);
-        Assert.Equal (0, selectCount);
-        Assert.Equal (0, acceptCount);
-
-        //     Make Selected != Cursor
-        Assert.True (Application.RaiseKeyDownEvent (Key.CursorDown));
-        Assert.Equal (0, rg.SelectedItem);
-        Assert.Equal (1, rg.Cursor);
-
-        otherView.SetFocus ();
-
-        //    Selected != Cursor - SetFocus
-        Assert.True (Application.RaiseKeyDownEvent (rg.HotKey));
-        Assert.True (rg.HasFocus);
-        Assert.Equal (0, rg.SelectedItem);
-        Assert.Equal (1, rg.Cursor);
-        Assert.Equal (0, selectedItemChangedCount);
-        Assert.Equal (0, selectCount);
-        Assert.Equal (0, acceptCount);
-
-        Assert.True (Application.RaiseKeyDownEvent (rg.HotKey));
-        Assert.True (rg.HasFocus);
-        Assert.Equal (1, rg.SelectedItem);
-        Assert.Equal (1, rg.Cursor);
-        Assert.Equal (1, selectedItemChangedCount);
-        Assert.Equal (1, selectCount);
-        Assert.Equal (0, acceptCount);
-
-        Application.ResetState (true);
-    }
-
-    [Fact]
-    public void HotKeys_HasFocus_False_Does_Not_SetFocus_Selects ()
-    {
-        Application.Navigation = new ();
-        var rg = new RadioGroup { RadioLabels = ["Item _A", "Item _B"] };
-        Application.Top = new ();
-
-        // With !HasFocus
-        View otherView = new () { Id = "otherView", CanFocus = true };
-
-        Label label = new ()
-        {
-            Id = "label",
-            Title = "_R"
-        };
-
-        Application.Top.Add (label, rg, otherView);
-        otherView.SetFocus ();
-
-        var selectedItemChangedCount = 0;
-        rg.SelectedItemChanged += (s, e) => selectedItemChangedCount++;
-
-        var selectCount = 0;
-        rg.Selecting += (s, e) => selectCount++;
-
-        var acceptCount = 0;
-        rg.Accepting += (s, e) => acceptCount++;
-
-        // By default the first item is selected
-        Assert.Equal (0, rg.SelectedItem);
-        Assert.Equal (Orientation.Vertical, rg.Orientation);
-        Assert.Equal (0, selectedItemChangedCount);
-        Assert.Equal (0, selectCount);
-        Assert.Equal (0, acceptCount);
-        Assert.Equal (Key.Empty, rg.HotKey);
-
-        Assert.False (rg.HasFocus);
-
-        // Test RadioTitem.HotKey - Should never SetFocus
-        //    Selected (0) == Cursor (0) 
-        Assert.True (Application.RaiseKeyDownEvent (Key.A));
-        Assert.False (rg.HasFocus);
-        Assert.Equal (0, rg.SelectedItem);
-        Assert.Equal (0, rg.Cursor);
-        Assert.Equal (0, selectedItemChangedCount);
-        Assert.Equal (0, selectCount);
-        Assert.Equal (0, acceptCount);
-
-        rg.SetFocus ();
-
-        //     Make Selected != Cursor
-        Assert.True (Application.RaiseKeyDownEvent (Key.CursorDown));
-        Assert.Equal (0, rg.SelectedItem);
-        Assert.Equal (1, rg.Cursor);
-
-        otherView.SetFocus ();
-
-        //    Selected != Cursor
-        Assert.True (Application.RaiseKeyDownEvent (Key.A));
-        Assert.False (rg.HasFocus);
-        Assert.Equal (0, rg.SelectedItem);
-        Assert.Equal (1, rg.Cursor);
-        Assert.Equal (0, selectedItemChangedCount);
-        Assert.Equal (0, selectCount);
-        Assert.Equal (0, acceptCount);
-
-        //    Selected != Cursor - Should not set focus
-        Assert.True (Application.RaiseKeyDownEvent (Key.B));
-        Assert.False (rg.HasFocus);
-        Assert.Equal (1, rg.SelectedItem);
-        Assert.Equal (1, rg.Cursor);
-        Assert.Equal (1, selectedItemChangedCount);
-        Assert.Equal (1, selectCount);
-        Assert.Equal (0, acceptCount);
-
-        Assert.True (Application.RaiseKeyDownEvent (Key.B));
-        Assert.False (rg.HasFocus);
-        Assert.Equal (1, rg.SelectedItem);
-        Assert.Equal (1, rg.Cursor);
-        Assert.Equal (1, selectedItemChangedCount);
-        Assert.Equal (1, selectCount);
-        Assert.Equal (0, acceptCount);
-
-        Application.ResetState (true);
-    }
-
-    [Fact]
-    public void HotKeys_HasFocus_True_Selects ()
-    {
-        var rg = new RadioGroup { RadioLabels = ["_Left", "_Right", "Cen_tered", "_Justified"] };
-        Application.Top = new ();
-        Application.Top.Add (rg);
-        rg.SetFocus ();
-
-        Assert.NotEmpty (rg.HotKeyBindings.GetCommands (KeyCode.L));
-        Assert.NotEmpty (rg.HotKeyBindings.GetCommands (KeyCode.L | KeyCode.ShiftMask));
-        Assert.NotEmpty (rg.HotKeyBindings.GetCommands (KeyCode.L | KeyCode.AltMask));
-
-        Assert.True (Application.RaiseKeyDownEvent (Key.T));
-        Assert.Equal (2, rg.SelectedItem);
-        Assert.True (Application.RaiseKeyDownEvent (Key.L));
-        Assert.Equal (0, rg.SelectedItem);
-        Assert.True (Application.RaiseKeyDownEvent (Key.J));
-        Assert.Equal (3, rg.SelectedItem);
-        Assert.True (Application.RaiseKeyDownEvent (Key.R));
-        Assert.Equal (1, rg.SelectedItem);
-
-        Assert.True (Application.RaiseKeyDownEvent (Key.T.WithAlt));
-        Assert.Equal (2, rg.SelectedItem);
-        Assert.True (Application.RaiseKeyDownEvent (Key.L.WithAlt));
-        Assert.Equal (0, rg.SelectedItem);
-        Assert.True (Application.RaiseKeyDownEvent (Key.J.WithAlt));
-        Assert.Equal (3, rg.SelectedItem);
-        Assert.True (Application.RaiseKeyDownEvent (Key.R.WithAlt));
-        Assert.Equal (1, rg.SelectedItem);
-
-        Application.Top.Remove (rg);
-        var superView = new View ();
-        superView.Add (rg);
-        Assert.True (superView.NewKeyDownEvent (Key.T));
-        Assert.Equal (2, rg.SelectedItem);
-        Assert.True (superView.NewKeyDownEvent (Key.L));
-        Assert.Equal (0, rg.SelectedItem);
-        Assert.True (superView.NewKeyDownEvent (Key.J));
-        Assert.Equal (3, rg.SelectedItem);
-        Assert.True (superView.NewKeyDownEvent (Key.R));
-        Assert.Equal (1, rg.SelectedItem);
-
-        Assert.True (superView.NewKeyDownEvent (Key.T.WithAlt));
-        Assert.Equal (2, rg.SelectedItem);
-        Assert.True (superView.NewKeyDownEvent (Key.L.WithAlt));
-        Assert.Equal (0, rg.SelectedItem);
-        Assert.True (superView.NewKeyDownEvent (Key.J.WithAlt));
-        Assert.Equal (3, rg.SelectedItem);
-        Assert.True (superView.NewKeyDownEvent (Key.R.WithAlt));
-        Assert.Equal (1, rg.SelectedItem);
-
-        Application.Top.Dispose ();
-    }
-
-    [Fact]
-    public void HotKey_SetsFocus ()
-    {
-        var superView = new View
-        {
-            CanFocus = true
-        };
-        superView.Add (new View { CanFocus = true });
-
-        var group = new RadioGroup
-        {
-            Title = "Radio_Group",
-            RadioLabels = ["_Left", "_Right", "Cen_tered", "_Justified"]
-        };
-        superView.Add (group);
-
-        Assert.False (group.HasFocus);
-        Assert.Equal (0, group.SelectedItem);
-
-        group.NewKeyDownEvent (Key.G.WithAlt);
-
-        Assert.Equal (0, group.SelectedItem);
-        Assert.True (group.HasFocus);
-    }
-
-    [Fact]
-    public void HotKey_No_SelectedItem_Selects_First ()
-    {
-        var superView = new View
-        {
-            CanFocus = true
-        };
-        superView.Add (new View { CanFocus = true });
-
-        var group = new RadioGroup
-        {
-            Title = "Radio_Group",
-            RadioLabels = ["_Left", "_Right", "Cen_tered", "_Justified"]
-        };
-        group.SelectedItem = -1;
-
-        superView.Add (group);
-
-        Assert.False (group.HasFocus);
-        Assert.Equal (-1, group.SelectedItem);
-
-        group.NewKeyDownEvent (Key.G.WithAlt);
-
-        Assert.Equal (0, group.SelectedItem);
-        Assert.False (group.HasFocus);
-    }
-
-    [Fact]
-    public void HotKeys_Does_Not_SetFocus ()
-    {
-        var superView = new View
-        {
-            CanFocus = true
-        };
-        superView.Add (new View { CanFocus = true });
-        var group = new RadioGroup { RadioLabels = new [] { "_Left", "_Right", "Cen_tered", "_Justified" } };
-        superView.Add (group);
-
-        Assert.False (group.HasFocus);
-        Assert.Equal (0, group.SelectedItem);
-
-        group.NewKeyDownEvent (Key.R);
-
-        Assert.Equal (1, group.SelectedItem);
-        Assert.False (group.HasFocus);
-    }
-
-    [Fact]
-    public void HotKey_Command_Does_Not_Accept ()
-    {
-        var group = new RadioGroup { RadioLabels = ["_Left", "_Right", "Cen_tered", "_Justified"] };
-        var accepted = false;
-
-        group.Accepting += OnAccept;
-        group.InvokeCommand (Command.HotKey);
-
-        Assert.False (accepted);
-
-        return;
-
-        void OnAccept (object sender, CommandEventArgs e) { accepted = true; }
-    }
-
-    [Fact]
-    public void Accept_Command_Fires_Accept ()
-    {
-        var group = new RadioGroup { RadioLabels = ["_Left", "_Right", "Cen_tered", "_Justified"] };
-        var accepted = false;
-
-        group.Accepting += OnAccept;
-        group.InvokeCommand (Command.Accept);
-
-        Assert.True (accepted);
-
-        return;
-
-        void OnAccept (object sender, CommandEventArgs e) { accepted = true; }
-    }
-
-    [Fact]
-    [AutoInitShutdown]
-    public void Orientation_Width_Height_Vertical_Horizontal_Space ()
-    {
-        var rg = new RadioGroup { RadioLabels = ["Test", "New Test 你"] };
-        var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () };
-        win.Add (rg);
-        var top = new Toplevel ();
-        top.Add (win);
-
-        Application.Begin (top);
-        Application.Driver!.SetScreenSize (30, 5);
-
-        Assert.Equal (Orientation.Vertical, rg.Orientation);
-        Assert.Equal (2, rg.RadioLabels.Length);
-        Assert.Equal (0, rg.X);
-        Assert.Equal (0, rg.Y);
-        Assert.Equal (13, rg.Frame.Width);
-        Assert.Equal (2, rg.Frame.Height);
-
-        var expected = @$"
-┌────────────────────────────┐
-│{Glyphs.Selected} Test                      │
-│{Glyphs.UnSelected} New Test 你               │
-│                            │
-└────────────────────────────┘
-";
-
-        Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output);
-        Assert.Equal (new (0, 0, 30, 5), pos);
-
-        rg.Orientation = Orientation.Horizontal;
-        AutoInitShutdownAttribute.RunIteration ();
-
-        Assert.Equal (Orientation.Horizontal, rg.Orientation);
-        Assert.Equal (2, rg.HorizontalSpace);
-        Assert.Equal (0, rg.X);
-        Assert.Equal (0, rg.Y);
-        Assert.Equal (21, rg.Frame.Width);
-        Assert.Equal (1, rg.Frame.Height);
-
-        expected = @$"
-┌────────────────────────────┐
-│{Glyphs.Selected} Test  {Glyphs.UnSelected} New Test 你       │
-│                            │
-│                            │
-└────────────────────────────┘
-";
-
-        pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output);
-        Assert.Equal (new (0, 0, 30, 5), pos);
-
-        rg.HorizontalSpace = 4;
-        AutoInitShutdownAttribute.RunIteration ();
-
-        Assert.Equal (Orientation.Horizontal, rg.Orientation);
-        Assert.Equal (4, rg.HorizontalSpace);
-        Assert.Equal (0, rg.X);
-        Assert.Equal (0, rg.Y);
-        Assert.Equal (23, rg.Frame.Width);
-        Assert.Equal (1, rg.Frame.Height);
-
-        expected = @$"
-┌────────────────────────────┐
-│{Glyphs.Selected} Test    {Glyphs.UnSelected} New Test 你     │
-│                            │
-│                            │
-└────────────────────────────┘
-";
-
-        pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output);
-        Assert.Equal (new (0, 0, 30, 5), pos);
-        top.Dispose ();
-    }
-
-    [Fact]
-    public void SelectedItemChanged_Event ()
-    {
-        int? previousSelectedItem = -1;
-        int? selectedItem = -1;
-        var rg = new RadioGroup { RadioLabels = ["Test", "New Test"] };
-
-        rg.SelectedItemChanged += (s, e) =>
-                                  {
-                                      previousSelectedItem = e.PreviousSelectedItem;
-                                      selectedItem = e.SelectedItem;
-                                  };
-
-        rg.SelectedItem = 1;
-        Assert.Equal (0, previousSelectedItem);
-        Assert.Equal (selectedItem, rg.SelectedItem);
-    }
-
-    #region Mouse Tests
-
-    [Fact]
-    [SetupFakeApplication]
-    public void Mouse_Click ()
-    {
-        var radioGroup = new RadioGroup
-        {
-            RadioLabels = ["_1", "_2"]
-        };
-        Assert.True (radioGroup.CanFocus);
-
-        var selectedItemChanged = 0;
-        radioGroup.SelectedItemChanged += (s, e) => selectedItemChanged++;
-
-        var selectingCount = 0;
-        radioGroup.Selecting += (s, e) => selectingCount++;
-
-        var acceptedCount = 0;
-        radioGroup.Accepting += (s, e) => acceptedCount++;
-
-        Assert.Equal (Orientation.Vertical, radioGroup.Orientation);
-
-        radioGroup.HasFocus = true;
-        Assert.True (radioGroup.HasFocus);
-        Assert.Equal (0, radioGroup.SelectedItem);
-        Assert.Equal (0, selectedItemChanged);
-        Assert.Equal (0, selectingCount);
-        Assert.Equal (0, acceptedCount);
-
-        // Click on the first item, which is already selected
-        Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Clicked }));
-        Assert.Equal (0, radioGroup.SelectedItem);
-        Assert.Equal (0, selectedItemChanged);
-        Assert.Equal (0, selectingCount);
-        Assert.Equal (0, acceptedCount);
-
-        // Click on the second item
-        Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 1), Flags = MouseFlags.Button1Clicked }));
-        Assert.Equal (1, radioGroup.SelectedItem);
-        Assert.Equal (1, selectedItemChanged);
-        Assert.Equal (1, selectingCount);
-        Assert.Equal (0, acceptedCount);
-
-        // Click on the first item
-        Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Clicked }));
-        Assert.Equal (0, radioGroup.SelectedItem);
-        Assert.Equal (2, selectedItemChanged);
-        Assert.Equal (2, selectingCount);
-        Assert.Equal (0, acceptedCount);
-    }
-
-    [Fact]
-    [SetupFakeApplication]
-    public void Mouse_DoubleClick_Accepts ()
-    {
-        var radioGroup = new RadioGroup
-        {
-            RadioLabels = ["_1", "__2"]
-        };
-        Assert.True (radioGroup.CanFocus);
-
-        var selectedItemChanged = 0;
-        radioGroup.SelectedItemChanged += (s, e) => selectedItemChanged++;
-
-        var selectingCount = 0;
-        radioGroup.Selecting += (s, e) => selectingCount++;
-
-        var acceptedCount = 0;
-        var handleAccepted = false;
-
-        radioGroup.Accepting += (s, e) =>
-                             {
-                                 acceptedCount++;
-                                 e.Handled = handleAccepted;
-                             };
-
-        Assert.True (radioGroup.DoubleClickAccepts);
-        Assert.Equal (Orientation.Vertical, radioGroup.Orientation);
-
-        radioGroup.HasFocus = true;
-        Assert.True (radioGroup.HasFocus);
-        Assert.Equal (0, radioGroup.SelectedItem);
-        Assert.Equal (0, selectedItemChanged);
-        Assert.Equal (0, selectingCount);
-        Assert.Equal (0, acceptedCount);
-
-        // NOTE: Drivers ALWAYS generate a Button1Clicked event before Button1DoubleClicked
-        // NOTE: We need to do the same
-
-        Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Clicked }));
-        Assert.False (radioGroup.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1DoubleClicked }));
-        Assert.Equal (0, radioGroup.SelectedItem);
-        Assert.Equal (0, selectedItemChanged);
-        Assert.Equal (0, selectingCount);
-        Assert.Equal (1, acceptedCount);
-
-        // single click twice
-        Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 1), Flags = MouseFlags.Button1Clicked }));
-        Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 1), Flags = MouseFlags.Button1Clicked }));
-        Assert.Equal (1, radioGroup.SelectedItem);
-        Assert.Equal (1, selectedItemChanged);
-        Assert.Equal (1, selectingCount);
-        Assert.Equal (1, acceptedCount);
-
-        Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 1), Flags = MouseFlags.Button1Clicked }));
-        Assert.False (radioGroup.NewMouseEvent (new () { Position = new (0, 1), Flags = MouseFlags.Button1DoubleClicked }));
-        Assert.Equal (1, radioGroup.SelectedItem);
-        Assert.Equal (1, selectedItemChanged);
-        Assert.Equal (1, selectingCount);
-        Assert.Equal (2, acceptedCount);
-
-        View superView = new () { Id = "superView", CanFocus = true };
-        superView.Add (radioGroup);
-        superView.SetFocus ();
-
-        Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Clicked }));
-        Assert.Equal (0, radioGroup.SelectedItem);
-        Assert.Equal (2, selectedItemChanged);
-        Assert.Equal (2, selectingCount);
-        Assert.Equal (2, acceptedCount);
-
-        var superViewAcceptCount = 0;
-
-        superView.Accepting += (s, a) =>
-                            {
-                                superViewAcceptCount++;
-                                a.Handled = true;
-                            };
-
-        Assert.Equal (0, superViewAcceptCount);
-
-        // By handling the event, we're cancelling it. So the radio group should not change.
-        handleAccepted = true;
-        Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Clicked }));
-        Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1DoubleClicked }));
-        Assert.Equal (0, radioGroup.SelectedItem);
-        Assert.Equal (2, selectedItemChanged);
-        Assert.Equal (2, selectingCount);
-        Assert.Equal (3, acceptedCount);
-        Assert.Equal (0, superViewAcceptCount);
-
-        handleAccepted = false;
-        Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Clicked }));
-        Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1DoubleClicked }));
-        Assert.Equal (0, radioGroup.SelectedItem);
-        Assert.Equal (2, selectedItemChanged);
-        Assert.Equal (2, selectingCount);
-        Assert.Equal (4, acceptedCount);
-        Assert.Equal (1, superViewAcceptCount); // Accept bubbles up to superview
-
-        radioGroup.DoubleClickAccepts = false;
-        Assert.True (radioGroup.NewMouseEvent (new () { Position = new (0, 1), Flags = MouseFlags.Button1Clicked }));
-        Assert.False (radioGroup.NewMouseEvent (new () { Position = new (0, 1), Flags = MouseFlags.Button1DoubleClicked }));
-    }
-
-    #endregion Mouse Tests
-}

+ 531 - 0
Tests/UnitTestsParallelizable/Drawing/Region/DrawOuterBoundaryTests.cs

@@ -0,0 +1,531 @@
+using System.Collections.Concurrent;
+using Xunit.Abstractions;
+
+namespace UnitTests_Parallelizable.DrawingTests;
+
+/// <summary>
+///     Tests for <see cref="Region.DrawOuterBoundary"/>.
+/// </summary>
+public class DrawOuterBoundaryTests (ITestOutputHelper output)
+{
+    private readonly ITestOutputHelper _output = output;
+
+    [Fact]
+    public void DrawOuterBoundary_AfterIntersect_DrawsIntersectedBoundary ()
+    {
+        // Arrange
+        var region = new Region (new (0, 0, 10, 10));
+        region.Intersect (new Rectangle (5, 5, 10, 10));
+        var canvas = new LineCanvas ();
+
+        // Act
+        region.DrawOuterBoundary (canvas, LineStyle.Single);
+
+        // Assert
+        Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
+        Assert.NotEmpty (cells);
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_AfterMinimalUnion_DrawsMinimalBoundary ()
+    {
+        // Arrange
+        var region = new Region (new (0, 0, 3, 3));
+        region.MinimalUnion (new Rectangle (3, 0, 3, 3));
+        var canvas = new LineCanvas ();
+
+        // Act
+        region.DrawOuterBoundary (canvas, LineStyle.Single);
+
+        // Assert
+        Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
+        Assert.NotEmpty (cells);
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_ComplexShape_HandlesMultipleRegions ()
+    {
+        // Arrange - Create a complex shape with multiple rectangles
+        var region = new Region (new (0, 0, 3, 3));
+        region.Union (new Rectangle (3, 3, 3, 3));
+        region.Union (new Rectangle (6, 0, 3, 3));
+        var canvas = new LineCanvas ();
+
+        // Act
+        region.DrawOuterBoundary (canvas, LineStyle.Single);
+
+        // Assert
+        Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
+        Assert.NotEmpty (cells);
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_DiagonallyConnectedRectangles_DrawsOuterBoundary ()
+    {
+        // Arrange - Test the specific case from BUGBUG comment: (0,0,3,3) and (3,3,3,3)
+        var region = new Region (new (0, 0, 3, 3));
+        region.Union (new Rectangle (3, 3, 3, 3));
+        var canvas = new LineCanvas ();
+
+        // Act
+        region.DrawOuterBoundary (canvas, LineStyle.Single);
+
+        // Assert
+        Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
+        Assert.NotEmpty (cells);
+
+        // Note: According to BUGBUG comment, this should draw specific shape
+        // with connecting corner but currently draws incorrectly
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_EmptyRegion_DoesNotThrow ()
+    {
+        // Arrange
+        var region = new Region ();
+        var canvas = new LineCanvas ();
+
+        // Act
+        Exception exception = Record.Exception (() => region.DrawOuterBoundary (canvas, LineStyle.Single));
+
+        // Assert
+        Assert.Null (exception);
+        Assert.Empty (canvas.GetCellMap ());
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_GridPattern_DrawsOuterBoundary ()
+    {
+        // Arrange - Create a checkerboard pattern
+        var region = new Region (new (0, 0, 2, 2));
+        region.Union (new Rectangle (4, 0, 2, 2));
+        region.Union (new Rectangle (0, 4, 2, 2));
+        region.Union (new Rectangle (4, 4, 2, 2));
+        var canvas = new LineCanvas ();
+
+        // Act
+        region.DrawOuterBoundary (canvas, LineStyle.Single);
+
+        // Assert
+        Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
+        Assert.NotEmpty (cells);
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_HollowRectangle_DrawsOuterAndInnerBoundaries ()
+    {
+        // Arrange - Create a hollow rectangle (outer rect with inner rect removed)
+        var region = new Region (new (0, 0, 10, 10));
+        region.Exclude (new Rectangle (2, 2, 6, 6));
+        var canvas = new LineCanvas ();
+
+        // Act
+        region.DrawOuterBoundary (canvas, LineStyle.Single);
+
+        // Assert
+        Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
+        Assert.NotEmpty (cells);
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_HorizontalLineRectangle_DrawsHorizontalLine ()
+    {
+        // Arrange - A horizontal line (width>1, height=1)
+        var region = new Region (new (0, 0, 4, 1));
+        var canvas = new LineCanvas ();
+
+        // Act
+        region.DrawOuterBoundary (canvas, LineStyle.Single);
+
+        // Assert
+        Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
+        Assert.NotEmpty (cells);
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_LShapedRegion_DrawsLShapeBoundary ()
+    {
+        // Arrange - Create an L-shape
+        var region = new Region (new (0, 0, 3, 3));
+        region.Union (new Rectangle (0, 3, 3, 3));
+        var canvas = new LineCanvas ();
+
+        // Act
+        region.DrawOuterBoundary (canvas, LineStyle.Single);
+
+        // Assert
+        Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
+        Assert.NotEmpty (cells);
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_MultipleCallsOnSameCanvas_AccumulatesLines ()
+    {
+        // Arrange
+        var region1 = new Region (new (0, 0, 3, 3));
+        var region2 = new Region (new (5, 5, 3, 3));
+        var canvas = new LineCanvas ();
+
+        // Act
+        region1.DrawOuterBoundary (canvas, LineStyle.Single);
+        int cellCountAfterFirst = canvas.GetCellMap ().Count;
+
+        region2.DrawOuterBoundary (canvas, LineStyle.Single);
+        int cellCountAfterSecond = canvas.GetCellMap ().Count;
+
+        // Assert
+        Assert.True (cellCountAfterSecond >= cellCountAfterFirst);
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_MultipleRegionsWithGaps_DrawsSeparateBoundaries ()
+    {
+        // Arrange
+        var region = new Region ();
+        region.Union (new Rectangle (0, 0, 2, 2));
+        region.Union (new Rectangle (4, 0, 2, 2));
+        region.Union (new Rectangle (8, 0, 2, 2));
+        var canvas = new LineCanvas ();
+
+        // Act
+        region.DrawOuterBoundary (canvas, LineStyle.Single);
+
+        // Assert
+        Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
+        Assert.NotEmpty (cells);
+
+        // Should have three separate boundary regions
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_OverlappingRectangles_DrawsOuterBoundaryOnly ()
+    {
+        // Arrange - Two overlapping rectangles
+        var region = new Region (new (0, 0, 5, 5));
+        region.Union (new Rectangle (3, 3, 5, 5));
+        var canvas = new LineCanvas ();
+
+        // Act
+        region.DrawOuterBoundary (canvas, LineStyle.Single);
+
+        // Assert
+        Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
+        Assert.NotEmpty (cells);
+
+        // Should only draw outer perimeter, not the overlapping internal area
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_RectangleAtNegativeCoordinates_DrawsBoundary ()
+    {
+        // Arrange
+        var region = new Region (new (-5, -5, 3, 3));
+        var canvas = new LineCanvas ();
+
+        // Act
+        region.DrawOuterBoundary (canvas, LineStyle.Single);
+
+        // Assert
+        Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
+        Assert.NotEmpty (cells);
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_SinglePixelRectangle_DrawsSinglePoint ()
+    {
+        // Arrange - A 1x1 rectangle
+        var region = new Region (new (5, 5, 1, 1));
+        var canvas = new LineCanvas ();
+
+        // Act
+        region.DrawOuterBoundary (canvas, LineStyle.Single);
+
+        // Assert
+        Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
+        Assert.NotEmpty (cells);
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_SingleRectangle_DrawsBoundary ()
+    {
+        // Arrange
+        var region = new Region (new (0, 0, 3, 3));
+        var canvas = new LineCanvas ();
+
+        // Act
+        region.DrawOuterBoundary (canvas, LineStyle.Single);
+
+        // Assert
+        Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
+        Assert.NotEmpty (cells);
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_SingleWidthRegion_DrawsCorrectly ()
+    {
+        // Arrange - Test the specific case mentioned in BUGBUG comment
+        var region = new Region (new (0, 0, 1, 4));
+        var canvas = new LineCanvas ();
+
+        // Act
+        region.DrawOuterBoundary (canvas, LineStyle.Single);
+
+        // Assert
+        Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
+        Assert.NotEmpty (cells);
+
+        // Note: According to BUGBUG, this should draw a single vertical line
+        // but currently draws too wide
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_ThreadSafety_MultipleThreadsDrawing ()
+    {
+        // Arrange
+        var region = new Region (new (0, 0, 10, 10));
+        ConcurrentBag<Exception> exceptions = new ();
+
+        // Act
+        Parallel.For (
+                      0,
+                      10,
+                      i =>
+                      {
+                          try
+                          {
+                              var canvas = new LineCanvas ();
+                              region.DrawOuterBoundary (canvas, LineStyle.Single);
+                          }
+                          catch (Exception ex)
+                          {
+                              exceptions.Add (ex);
+                          }
+                      });
+
+        // Assert
+        Assert.Empty (exceptions);
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_ThreeWidthRegion_DrawsCorrectly ()
+    {
+        // Arrange - Test the specific case mentioned in BUGBUG comment
+        var region = new Region (new (20, 0, 3, 4));
+        var canvas = new LineCanvas ();
+
+        // Act
+        region.DrawOuterBoundary (canvas, LineStyle.Single);
+
+        // Assert
+        Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
+        Assert.NotEmpty (cells);
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_TShapedRegion_DrawsCorrectBoundary ()
+    {
+        // Arrange - Create a T-shape
+        var region = new Region (new (0, 0, 9, 3)); // Horizontal bar
+        region.Union (new Rectangle (3, 3, 3, 6)); // Vertical bar
+        var canvas = new LineCanvas ();
+
+        // Act
+        region.DrawOuterBoundary (canvas, LineStyle.Single);
+
+        // Assert
+        Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
+        Assert.NotEmpty (cells);
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_TwoAdjacentRectangles_DrawsOuterPerimeter ()
+    {
+        // Arrange - Two rectangles side by side
+        var region = new Region (new (0, 0, 3, 3));
+        region.Union (new Rectangle (3, 0, 3, 3));
+        var canvas = new LineCanvas ();
+
+        // Act
+        region.DrawOuterBoundary (canvas, LineStyle.Single);
+
+        // Assert
+        Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
+        Assert.NotEmpty (cells);
+
+        // Should draw outer boundary, not internal dividing line
+        // The combined region should be treated as one shape
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_TwoSeparateRectangles_DrawsTwoBoundaries ()
+    {
+        // Arrange - Two non-adjacent rectangles
+        var region = new Region (new (0, 0, 2, 2));
+        region.Union (new Rectangle (5, 5, 2, 2));
+        var canvas = new LineCanvas ();
+
+        // Act
+        region.DrawOuterBoundary (canvas, LineStyle.Single);
+
+        // Assert
+        Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
+        Assert.NotEmpty (cells);
+
+        // Should have boundaries for both rectangles
+        Assert.True (cells.Count > 0);
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_TwoWidthRegion_DrawsCorrectly ()
+    {
+        // Arrange - Test the specific case mentioned in BUGBUG comment
+        var region = new Region (new (10, 0, 2, 4));
+        var canvas = new LineCanvas ();
+
+        // Act
+        region.DrawOuterBoundary (canvas, LineStyle.Single);
+
+        // Assert
+        Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
+        Assert.NotEmpty (cells);
+    }
+
+    [Theory]
+    [InlineData (0, 0)]
+    [InlineData (10, 10)]
+    [InlineData (-5, -5)]
+    [InlineData (100, 100)]
+    public void DrawOuterBoundary_VariousPositions_DrawsBoundary (int x, int y)
+    {
+        // Arrange
+        var region = new Region (new (x, y, 5, 5));
+        var canvas = new LineCanvas ();
+
+        // Act
+        Exception exception = Record.Exception (() => region.DrawOuterBoundary (canvas, LineStyle.Single));
+
+        // Assert
+        Assert.Null (exception);
+        Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
+        Assert.NotEmpty (cells);
+    }
+
+    [Theory]
+    [InlineData (1, 1)]
+    [InlineData (1, 5)]
+    [InlineData (5, 1)]
+    [InlineData (2, 2)]
+    [InlineData (10, 10)]
+    [InlineData (100, 100)]
+    public void DrawOuterBoundary_VariousSizes_DrawsBoundary (int width, int height)
+    {
+        // Arrange
+        var region = new Region (new (0, 0, width, height));
+        var canvas = new LineCanvas ();
+
+        // Act
+        Exception exception = Record.Exception (() => region.DrawOuterBoundary (canvas, LineStyle.Single));
+
+        // Assert
+        Assert.Null (exception);
+        Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
+        Assert.NotEmpty (cells);
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_VerticalLineRectangle_DrawsVerticalLine ()
+    {
+        // Arrange - A vertical line (width=1, height>1)
+        var region = new Region (new (0, 0, 1, 4));
+        var canvas = new LineCanvas ();
+
+        // Act
+        region.DrawOuterBoundary (canvas, LineStyle.Single);
+
+        // Assert
+        Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
+        Assert.NotEmpty (cells);
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_VeryLargeRegion_FallsBackToDrawBoundaries ()
+    {
+        // Arrange - Create a region larger than the 1000x1000 threshold
+        var region = new Region (new (0, 0, 1100, 1100));
+        var canvas = new LineCanvas ();
+
+        // Act
+        Exception exception = Record.Exception (() => region.DrawOuterBoundary (canvas, LineStyle.Single));
+
+        // Assert
+        Assert.Null (exception);
+
+        // Should fall back to DrawBoundaries for very large regions
+        Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
+        Assert.NotEmpty (cells);
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_WithCustomAttribute_AppliesAttribute ()
+    {
+        // Arrange
+        var region = new Region (new (0, 0, 3, 3));
+        var canvas = new LineCanvas ();
+        var attribute = new Attribute (Color.Red, Color.Blue);
+
+        // Act
+        region.DrawOuterBoundary (canvas, LineStyle.Single, attribute);
+
+        // Assert
+        Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
+        Assert.NotEmpty (cells);
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_WithDifferentLineStyles_DrawsWithCorrectStyle ()
+    {
+        // Arrange
+        var region = new Region (new (0, 0, 3, 3));
+
+        // Test each line style
+        foreach (LineStyle style in Enum.GetValues<LineStyle> ())
+        {
+            var canvas = new LineCanvas ();
+
+            // Act
+            region.DrawOuterBoundary (canvas, style);
+
+            // Assert
+            Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
+            Assert.NotEmpty (cells);
+        }
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_ZeroHeightRectangle_HandlesGracefully ()
+    {
+        // Arrange - Rectangle with zero height
+        var region = new Region (new (5, 5, 5, 0));
+        var canvas = new LineCanvas ();
+
+        // Act
+        Exception exception = Record.Exception (() => region.DrawOuterBoundary (canvas, LineStyle.Single));
+
+        // Assert
+        Assert.Null (exception);
+    }
+
+    [Fact]
+    public void DrawOuterBoundary_ZeroWidthRectangle_HandlesGracefully ()
+    {
+        // Arrange - Rectangle with zero width
+        var region = new Region (new (5, 5, 0, 5));
+        var canvas = new LineCanvas ();
+
+        // Act
+        Exception exception = Record.Exception (() => region.DrawOuterBoundary (canvas, LineStyle.Single));
+
+        // Assert
+        Assert.Null (exception);
+    }
+}

+ 87 - 13
Tests/UnitTestsParallelizable/View/Navigation/AdvanceFocusTests.cs

@@ -127,17 +127,11 @@ public class AdvanceFocusTests ()
 
         // Cycle through v1 & v2
         top.AdvanceFocus (NavigationDirection.Forward, behavior);
-        Assert.True (v1.HasFocus);
-        Assert.False (v2.HasFocus);
-        Assert.False (v3.HasFocus);
+        Assert.Equal (v1, compoundSubView.Focused);
         top.AdvanceFocus (NavigationDirection.Forward, behavior);
-        Assert.False (v1.HasFocus);
-        Assert.True (v2.HasFocus);
-        Assert.False (v3.HasFocus);
+        Assert.Equal (v2, compoundSubView.Focused);
         top.AdvanceFocus (NavigationDirection.Forward, behavior);
-        Assert.True (v1.HasFocus);
-        Assert.False (v2.HasFocus);
-        Assert.False (v3.HasFocus);
+        Assert.Equal (v1, compoundSubView.Focused);
 
         // Add another subview
         View otherSubView = new ()
@@ -182,6 +176,12 @@ public class AdvanceFocusTests ()
     [Fact]
     public void AdvanceFocus_CompoundCompound_SubView_TabStop ()
     {
+        //top
+        //    ├── topv1, topv2, topv3 (only topv1 and topv2 are focusable)
+        //    └── compoundSubView
+        //        ├── v1, v2, v3 (v3 is not focusable)
+        //        └── compoundCompoundSubView
+        //            └── v4, v5, v6 (v6 is not focusable)
         TabBehavior behavior = TabBehavior.TabStop;
         var top = new View { Id = "top", CanFocus = true };
         var topv1 = new View { Id = "topv1", CanFocus = true, TabStop = behavior };
@@ -237,9 +237,9 @@ public class AdvanceFocusTests ()
 
         // Should cycle back to topv1
         top.AdvanceFocus (NavigationDirection.Forward, behavior);
-        Assert.True (topv1.HasFocus);
+        Assert.Equal (topv1, top.Focused);
         top.AdvanceFocus (NavigationDirection.Forward, behavior);
-        Assert.True (topv2.HasFocus);
+        Assert.Equal (topv2, top.Focused);
 
         // Add another top subview. Should cycle to it after v5
         View otherSubView = new ()
@@ -274,6 +274,80 @@ public class AdvanceFocusTests ()
         top.Dispose ();
     }
 
+    [Fact]
+    public void FocusNavigation_Should_Cycle_Back_To_Top_Level_Views ()
+    {
+        // Create a simplified version of the hierarchy from CompoundCompound test
+        // top
+        //  ├── topButton1, topButton2
+        //  └── nestedContainer
+        //       └── innerButton
+
+        // Create top-level view
+        View top = new ()
+        {
+            Id = "top",
+            CanFocus = true
+        };
+
+        // Create top-level buttons
+        View topButton1 = new ()
+        {
+            Id = "topButton1",
+            CanFocus = true,
+            TabStop = TabBehavior.TabStop
+        };
+
+        View topButton2 = new ()
+        {
+            Id = "topButton2",
+            CanFocus = true,
+            TabStop = TabBehavior.TabStop
+        };
+
+        // Create nested container with a button inside
+        View nestedContainer = new ()
+        {
+            Id = "nestedContainer",
+            CanFocus = true,
+            TabStop = TabBehavior.TabStop
+        };
+
+        View innerButton = new ()
+        {
+            Id = "innerButton",
+            CanFocus = true,
+            TabStop = TabBehavior.TabStop
+        };
+
+        // Build the view hierarchy
+        nestedContainer.Add (innerButton);
+        top.Add (topButton1, topButton2, nestedContainer);
+
+        // Initial focus on topButton1
+        topButton1.SetFocus ();
+        Assert.Equal (topButton1, top.Focused);
+
+        // Advance focus to topButton2
+        top.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
+        Assert.Equal (topButton2, top.Focused);
+
+        // Advance focus to innerButton
+        top.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
+        Assert.Equal (innerButton, nestedContainer.Focused);
+        Assert.Equal (nestedContainer, top.Focused);
+
+        // THIS IS WHERE THE BUG OCCURS
+        // Advancing focus from innerButton should go back to topButton1
+        top.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
+
+        // This assertion will fail with current implementation
+        Assert.Equal (topButton1, top.Focused);
+
+        top.Dispose ();
+    }
+
+
     [Fact]
     public void AdvanceFocus_Compound_SubView_TabGroup ()
     {
@@ -322,7 +396,7 @@ public class AdvanceFocusTests ()
         // TabGroup navs to the other subview
         top.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup);
         Assert.Equal (compoundSubView, top.Focused);
-        Assert.True (tabStopView.HasFocus); 
+        Assert.True (tabStopView.HasFocus);
 
         top.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup);
         Assert.Equal (compoundSubView, top.Focused);
@@ -330,7 +404,7 @@ public class AdvanceFocusTests ()
 
         top.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup);
         Assert.Equal (compoundSubView, top.Focused);
-        Assert.True (tabGroupView2.HasFocus); 
+        Assert.True (tabGroupView2.HasFocus);
 
         // Now go backwards
         top.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabGroup);

+ 4 - 4
Tests/UnitTestsParallelizable/View/Orientation/OrientationTests.cs

@@ -98,17 +98,17 @@ public class OrientationTests
     public void OrientationChanging_VirtualMethodCalledBeforeEvent ()
     {
         // Arrange
-        var radioGroup = new CustomView ();
+        var optionSelector = new CustomView ();
         bool eventCalled = false;
 
-        radioGroup.OrientationChanging += (sender, e) =>
+        optionSelector.OrientationChanging += (sender, e) =>
                                           {
                                               eventCalled = true;
-                                              Assert.True (radioGroup.OnOrientationChangingCalled, "OnOrientationChanging was not called before the event.");
+                                              Assert.True (optionSelector.OnOrientationChangingCalled, "OnOrientationChanging was not called before the event.");
                                           };
 
         // Act
-        radioGroup.Orientation = Orientation.Horizontal;
+        optionSelector.Orientation = Orientation.Horizontal;
 
         // Assert
         Assert.True (eventCalled, "OrientationChanging event was not called.");

+ 1 - 1
Tests/UnitTestsParallelizable/Views/CheckBoxTests.cs

@@ -254,7 +254,7 @@ public class CheckBoxTests ()
         Assert.Equal (0, selectCount);
         Assert.Equal (0, acceptCount);
 
-        Assert.True (checkBox.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1DoubleClicked }));
+        checkBox.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1DoubleClicked });
 
         Assert.Equal (CheckState.UnChecked, checkBox.CheckedState);
         Assert.Equal (0, checkedStateChangingCount);

+ 601 - 99
Tests/UnitTestsParallelizable/Views/FlagSelectorTests.cs

@@ -1,3 +1,4 @@
+#nullable enable
 namespace UnitTests_Parallelizable.ViewsTests;
 
 public class FlagSelectorTests
@@ -13,202 +14,703 @@ public class FlagSelectorTests
         Assert.Equal (Orientation.Vertical, flagSelector.Orientation);
     }
 
+
     [Fact]
-    public void SetFlags_WithDictionary_ShouldSetFlags ()
+    public void Value_Set_ShouldUpdateCheckedState ()
     {
         var flagSelector = new FlagSelector ();
-        var flags = new Dictionary<uint, string>
+        var flags = new Dictionary<int, string>
         {
             { 1, "Flag1" },
             { 2, "Flag2" }
         };
 
-        flagSelector.SetFlags (flags);
+        flagSelector.Values = flags.Keys.ToList ();
+        flagSelector.Labels = flags.Values.ToList ();
+        flagSelector.Value = 1;
+
+        var checkBox = flagSelector.SubViews.OfType<CheckBox> ().First (cb => (int)cb.Data! == 1);
+        Assert.Equal (CheckState.Checked, checkBox.CheckedState);
 
-        Assert.Equal (flags, flagSelector.Flags);
+        checkBox = flagSelector.SubViews.OfType<CheckBox> ().First (cb => (int)cb.Data! == 2);
+        Assert.Equal (CheckState.UnChecked, checkBox.CheckedState);
     }
 
     [Fact]
-    public void SetFlags_WithDictionary_ShouldSetValue ()
+    public void Styles_Set_ShouldCreateSubViews ()
     {
         var flagSelector = new FlagSelector ();
-        var flags = new Dictionary<uint, string>
+        var flags = new Dictionary<int, string>
         {
             { 1, "Flag1" },
             { 2, "Flag2" }
         };
 
-        flagSelector.SetFlags (flags);
+        flagSelector.Values = flags.Keys.ToList ();
+        flagSelector.Labels = flags.Values.ToList ();
+        flagSelector.Styles = SelectorStyles.ShowNoneFlag;
 
-        Assert.Equal ((uint)1, flagSelector.Value);
+        Assert.Contains (flagSelector.SubViews, sv => sv is CheckBox cb && cb.Title == "None");
     }
 
     [Fact]
-    public void SetFlags_WithEnum_ShouldSetFlags ()
+    public void ValueChanged_Event_ShouldBeRaised ()
     {
         var flagSelector = new FlagSelector ();
+        var flags = new Dictionary<int, string>
+        {
+            { 1, "Flag1" },
+            { 2, "Flag2" }
+        };
+
+        flagSelector.Values = flags.Keys.ToList ();
+        flagSelector.Labels = flags.Values.ToList ();
+        bool eventRaised = false;
+        flagSelector.ValueChanged += (sender, args) => eventRaised = true;
+
+        flagSelector.Value = 2;
+
+        Assert.True (eventRaised);
+    }
+
+    // Tests for FlagSelector<TEnum>
+    [Fact]
+    public void GenericInitialization_ShouldSetDefaults ()
+    {
+        var flagSelector = new FlagSelector<SelectorStyles> ();
+
+        Assert.True (flagSelector.CanFocus);
+        Assert.Equal (Dim.Auto (DimAutoStyle.Content), flagSelector.Width);
+        Assert.Equal (Dim.Auto (DimAutoStyle.Content), flagSelector.Height);
+        Assert.Equal (Orientation.Vertical, flagSelector.Orientation);
+    }
+
+    [Fact]
+    public void GenericSetFlagNames_ShouldSetFlagNames ()
+    {
+        var flagSelector = new FlagSelector<SelectorStyles> ();
+        flagSelector.Labels = Enum.GetValues<SelectorStyles> ()
+                                 .Select (
+                                          l => l switch
+                                               {
+                                                   SelectorStyles.None => "_No Style",
+                                                   SelectorStyles.ShowNoneFlag => "_Show None Value Style",
+                                                   SelectorStyles.ShowValue => "Show _Value Editor Style",
+                                                   SelectorStyles.All => "_All Styles",
+                                                   _ => l.ToString ()
+                                               }).ToList ();
+
+        Dictionary<int, string> expectedFlags = Enum.GetValues<SelectorStyles> ()
+                                                    .ToDictionary (f => Convert.ToInt32 (f), f => f switch
+                                                                                                  {
+                                                                                                      SelectorStyles.None => "_No Style",
+                                                                                                      SelectorStyles.ShowNoneFlag => "_Show None Value Style",
+                                                                                                      SelectorStyles.ShowValue => "Show _Value Editor Style",
+                                                                                                      SelectorStyles.All => "_All Styles",
+                                                                                                      _ => f.ToString ()
+                                                                                                  });
+
+        Assert.Equal (expectedFlags.Keys, flagSelector.Values);
+    }
+
+    [Fact]
+    public void GenericValue_Set_ShouldUpdateCheckedState ()
+    {
+        var flagSelector = new FlagSelector<SelectorStyles> ();
+
+        flagSelector.Value = SelectorStyles.ShowNoneFlag;
+
+        var checkBox = flagSelector.SubViews.OfType<CheckBox> ().First (cb => (int)cb.Data! == Convert.ToInt32 (SelectorStyles.ShowNoneFlag));
+        Assert.Equal (CheckState.Checked, checkBox.CheckedState);
+
+        checkBox = flagSelector.SubViews.OfType<CheckBox> ().First (cb => (int)cb.Data! == Convert.ToInt32 (SelectorStyles.ShowValue));
+        Assert.Equal (CheckState.UnChecked, checkBox.CheckedState);
+    }
+
+    [Fact]
+    public void GenericValueChanged_Event_ShouldBeRaised ()
+    {
+        var flagSelector = new FlagSelector<SelectorStyles> ();
 
-        flagSelector.SetFlags<FlagSelectorStyles> ();
+        bool eventRaised = false;
+        flagSelector.ValueChanged += (sender, args) => eventRaised = true;
 
-        var expectedFlags = Enum.GetValues<FlagSelectorStyles> ()
-                                .ToDictionary (f => Convert.ToUInt32 (f), f => f.ToString ());
+        flagSelector.Value = SelectorStyles.ShowNoneFlag;
 
-        Assert.Equal (expectedFlags, flagSelector.Flags);
+        Assert.True (eventRaised);
     }
 
     [Fact]
-    public void SetFlags_WithEnumAndCustomNames_ShouldSetFlags ()
+    public void Constructors_Defaults ()
     {
         var flagSelector = new FlagSelector ();
+        Assert.True (flagSelector.CanFocus);
+        Assert.Null (flagSelector.Values);
+        Assert.Equal (Rectangle.Empty, flagSelector.Frame);
+        Assert.Null (flagSelector.Value);
+
+        flagSelector = new ();
+        flagSelector.Values = [1];
+        flagSelector.Labels = ["Flag1"];
 
-        flagSelector.SetFlags<FlagSelectorStyles> (f => f switch
+        Assert.True (flagSelector.CanFocus);
+        Assert.Single (flagSelector.Values!);
+        Assert.Equal ((int)1, flagSelector.Value);
+
+        flagSelector = new ()
         {
-            FlagSelectorStyles.ShowNone => "Show None Value",
-            FlagSelectorStyles.ShowValueEdit => "Show Value Editor",
-            FlagSelectorStyles.All => "Everything",
-            _ => f.ToString ()
-        });
+            X = 1,
+            Y = 2,
+            Width = 20,
+            Height = 5,
+        };
+        flagSelector.Values = [1];
+        flagSelector.Labels = ["Flag1"];
 
-        var expectedFlags = Enum.GetValues<FlagSelectorStyles> ()
-                                .ToDictionary (f => Convert.ToUInt32 (f), f => f switch
-                                {
-                                    FlagSelectorStyles.ShowNone => "Show None Value",
-                                    FlagSelectorStyles.ShowValueEdit => "Show Value Editor",
-                                    FlagSelectorStyles.All => "Everything",
-                                    _ => f.ToString ()
-                                });
+        Assert.True (flagSelector.CanFocus);
+        Assert.Single (flagSelector.Values!);
+        Assert.Equal (new (1, 2, 20, 5), flagSelector.Frame);
+        Assert.Equal ((int)1, flagSelector.Value);
+
+        flagSelector = new () { X = 1, Y = 2 };
+        flagSelector.Values = [1];
+        flagSelector.Labels = ["Flag1"];
 
-        Assert.Equal (expectedFlags, flagSelector.Flags);
+        var view = new View { Width = 30, Height = 40 };
+        view.Add (flagSelector);
+        view.BeginInit ();
+        view.EndInit ();
+        view.LayoutSubViews ();
+
+        Assert.True (flagSelector.CanFocus);
+        Assert.Single (flagSelector.Values!);
+        Assert.Equal (new (1, 2, 7, 1), flagSelector.Frame);
+        Assert.Equal ((int)1, flagSelector.Value);
     }
 
     [Fact]
-    public void Value_Set_ShouldUpdateCheckedState ()
+    public void HotKey_SetsFocus ()
     {
-        var flagSelector = new FlagSelector ();
-        var flags = new Dictionary<uint, string>
+        var superView = new View
         {
-            { 1, "Flag1" },
-            { 2, "Flag2" }
+            CanFocus = true
         };
+        superView.Add (new View { CanFocus = true });
 
-        flagSelector.SetFlags (flags);
-        flagSelector.Value = 1;
+        var flagSelector = new FlagSelector ()
+        {
+            Title = "_FlagSelector",
+        };
+        flagSelector.Values = [0, 1];
+        flagSelector.Labels = ["_Left", "_Right"];
 
-        var checkBox = flagSelector.SubViews.OfType<CheckBox> ().First (cb => (uint)cb.Data == 1);
-        Assert.Equal (CheckState.Checked, checkBox.CheckedState);
+        superView.Add (flagSelector);
 
-        checkBox = flagSelector.SubViews.OfType<CheckBox> ().First (cb => (uint)cb.Data == 2);
-        Assert.Equal (CheckState.UnChecked, checkBox.CheckedState);
+        Assert.False (flagSelector.HasFocus);
+        Assert.Equal ((int)0, flagSelector.Value);
+
+        flagSelector.NewKeyDownEvent (Key.F.WithAlt);
+
+        Assert.Equal ((int)0, flagSelector.Value);
+        Assert.True (flagSelector.HasFocus);
     }
 
     [Fact]
-    public void Styles_Set_ShouldCreateSubViews ()
+    public void HotKey_Null_Value_Does_Not_Change_Value ()
     {
-        var flagSelector = new FlagSelector ();
-        var flags = new Dictionary<uint, string>
+        var superView = new View
         {
-            { 1, "Flag1" },
-            { 2, "Flag2" }
+            CanFocus = true
+        };
+        superView.Add (new View { CanFocus = true });
+
+        var flagSelector = new FlagSelector ()
+        {
+            Title = "_FlagSelector",
         };
+        flagSelector.Values = [0, 1];
+        flagSelector.Labels = ["_Left", "_Right"];
+        flagSelector.Value = null;
 
-        flagSelector.SetFlags (flags);
-        flagSelector.Styles = FlagSelectorStyles.ShowNone;
+        superView.Add (flagSelector);
 
-        Assert.Contains (flagSelector.SubViews, sv => sv is CheckBox cb && cb.Title == "None");
+        Assert.False (flagSelector.HasFocus);
+        Assert.Null (flagSelector.Value);
+
+        flagSelector.InvokeCommand (Command.HotKey, new KeyBinding ());
+
+        Assert.True (flagSelector.HasFocus);
+        Assert.Null (flagSelector.Value);
     }
 
+
     [Fact]
-    public void ValueChanged_Event_ShouldBeRaised ()
+    public void Set_Value_Sets ()
     {
+        var superView = new View
+        {
+            CanFocus = true
+        };
+        superView.Add (new View { CanFocus = true });
         var flagSelector = new FlagSelector ();
-        var flags = new Dictionary<uint, string>
+        flagSelector.Labels = ["_Left", "_Right"];
+        superView.Add (flagSelector);
+
+        Assert.False (flagSelector.HasFocus);
+        Assert.Null (flagSelector.Value);
+
+        flagSelector.Value = 1;
+
+        Assert.False (flagSelector.HasFocus);
+        Assert.Equal (1, flagSelector.Value);
+    }
+
+    [Fact]
+    public void Item_HotKey_Null_Value_Changes_Value_And_SetsFocus ()
+    {
+        var superView = new View
         {
-            { 1, "Flag1" },
-            { 2, "Flag2" }
+            CanFocus = true
         };
+        superView.Add (new View { CanFocus = true });
+        var flagSelector = new FlagSelector ();
+        flagSelector.Labels = ["_Left", "_Right"];
+        superView.Add (flagSelector);
 
-        flagSelector.SetFlags (flags);
-        bool eventRaised = false;
-        flagSelector.ValueChanged += (sender, args) => eventRaised = true;
+        Assert.False (flagSelector.HasFocus);
+        Assert.Null (flagSelector.Value);
 
-        flagSelector.Value = 2;
+        flagSelector.NewKeyDownEvent (Key.R);
 
-        Assert.True (eventRaised);
+        Assert.True (flagSelector.HasFocus);
+        Assert.Equal (1, flagSelector.Value);
     }
 
-    // Tests for FlagSelector<TEnum>
     [Fact]
-    public void GenericInitialization_ShouldSetDefaults ()
+    public void HotKey_Command_Does_Not_Accept ()
     {
-        var flagSelector = new FlagSelector<FlagSelectorStyles> ();
+        var flagSelector = new FlagSelector ();
+        flagSelector.Values = [0, 1];
+        flagSelector.Labels = ["_Left", "_Right"];
+        var accepted = false;
 
-        Assert.True (flagSelector.CanFocus);
-        Assert.Equal (Dim.Auto (DimAutoStyle.Content), flagSelector.Width);
-        Assert.Equal (Dim.Auto (DimAutoStyle.Content), flagSelector.Height);
-        Assert.Equal (Orientation.Vertical, flagSelector.Orientation);
+        flagSelector.Accepting += OnAccept;
+        flagSelector.InvokeCommand (Command.HotKey);
+
+        Assert.False (accepted);
+
+        return;
+
+        void OnAccept (object? sender, CommandEventArgs e) { accepted = true; }
     }
 
     [Fact]
-    public void Generic_SetFlags_Methods_Throw ()
+    public void Accept_Command_Fires_Accept ()
     {
-        var flagSelector = new FlagSelector<FlagSelectorStyles> ();
+        var flagSelector = new FlagSelector ();
+        flagSelector.Values = [0, 1];
+        flagSelector.Labels = ["_Left", "_Right"];
+        var accepted = false;
 
-        Assert.Throws<InvalidOperationException> (() => flagSelector.SetFlags (new Dictionary<uint, string> ()));
-        Assert.Throws<InvalidOperationException> (() => flagSelector.SetFlags<FlagSelectorStyles> ());
-        Assert.Throws<InvalidOperationException> (() => flagSelector.SetFlags<FlagSelectorStyles> (styles => null));
+        flagSelector.Accepting += OnAccept;
+        flagSelector.InvokeCommand (Command.Accept);
+
+        Assert.True (accepted);
+
+        return;
+
+        void OnAccept (object? sender, CommandEventArgs e) { accepted = true; }
     }
 
     [Fact]
-    public void GenericSetFlagNames_ShouldSetFlagNames ()
+    public void ValueChanged_Event ()
     {
-        var flagSelector = new FlagSelector<FlagSelectorStyles> ();
+        int? newValue = null;
+        var flagSelector = new FlagSelector ();
+        flagSelector.Values = [0, 1];
+        flagSelector.Labels = ["_Left", "_Right"];
 
-        flagSelector.SetFlagNames (f => f switch
+        flagSelector.ValueChanged += (s, e) =>
         {
-            FlagSelectorStyles.ShowNone => "Show None Value",
-            FlagSelectorStyles.ShowValueEdit => "Show Value Editor",
-            FlagSelectorStyles.All => "Everything",
-            _ => f.ToString ()
-        });
+            newValue = e.Value;
+        };
 
-        var expectedFlags = Enum.GetValues<FlagSelectorStyles> ()
-                                .ToDictionary (f => Convert.ToUInt32 (f), f => f switch
-                                {
-                                    FlagSelectorStyles.ShowNone => "Show None Value",
-                                    FlagSelectorStyles.ShowValueEdit => "Show Value Editor",
-                                    FlagSelectorStyles.All => "Everything",
-                                    _ => f.ToString ()
-                                });
+        flagSelector.Value = 1;
+        Assert.Equal (newValue, flagSelector.Value);
+    }
+
+    #region Mouse Tests
+
+
+    [Fact]
+    public void Mouse_DoubleClick_Accepts ()
+    {
 
-        Assert.Equal (expectedFlags, flagSelector.Flags);
     }
 
+
+
     [Fact]
-    public void GenericValue_Set_ShouldUpdateCheckedState ()
+    public void Mouse_Click_On_Activated_NoneFlag_Does_Nothing ()
+    {
+        FlagSelector selector = new ();
+        selector.Styles = SelectorStyles.ShowNoneFlag;
+        List<string> options = ["Flag1", "Flag2"];
+
+        selector.Labels = options;
+        selector.Layout ();
+
+        CheckBox checkBox = selector.SubViews.OfType<CheckBox> ().First (cb => cb.Title == "Flag1");
+        Assert.Null (selector.Value);
+        Assert.Equal (CheckState.UnChecked, checkBox.CheckedState);
+        selector.Value = 0;
+
+        var mouseEvent = new MouseEventArgs
+        {
+            Position = checkBox.Frame.Location,
+            Flags = MouseFlags.Button1Clicked
+        };
+
+        checkBox.NewMouseEvent (mouseEvent);
+
+        Assert.Equal (0, selector.Value);
+        Assert.Equal (CheckState.Checked, checkBox.CheckedState);
+        Assert.Equal (CheckState.UnChecked, selector.SubViews.OfType<CheckBox> ().First (cb => cb.Title == "Flag2").CheckedState);
+    }
+
+
+    [Fact]
+    public void Mouse_Click_On_NotActivated_NoneFlag_Toggles ()
     {
-        var flagSelector = new FlagSelector<FlagSelectorStyles> ();
+        FlagSelector selector = new ();
+        selector.Styles = SelectorStyles.ShowNoneFlag;
+        List<string> options = ["Flag1", "Flag2"];
+
+        selector.Labels = options;
+        selector.Layout ();
+
+        CheckBox checkBox = selector.SubViews.OfType<CheckBox> ().First (cb => cb.Title == "Flag1");
+        Assert.Null (selector.Value);
+        Assert.Equal (CheckState.UnChecked, checkBox.CheckedState);
+        selector.Value = 0;
+        Assert.Equal (CheckState.Checked, checkBox.CheckedState);
+
+        var mouseEvent = new MouseEventArgs
+        {
+            Position = checkBox.Frame.Location,
+            Flags = MouseFlags.Button1Clicked
+        };
 
-        flagSelector.SetFlagNames (f => f.ToString ());
-        flagSelector.Value = FlagSelectorStyles.ShowNone;
+        checkBox.NewMouseEvent (mouseEvent);
 
-        var checkBox = flagSelector.SubViews.OfType<CheckBox> ().First (cb => (uint)cb.Data == Convert.ToUInt32 (FlagSelectorStyles.ShowNone));
+        Assert.Equal (0, selector.Value);
         Assert.Equal (CheckState.Checked, checkBox.CheckedState);
+        Assert.Equal (CheckState.UnChecked, selector.SubViews.OfType<CheckBox> ().First (cb => cb.Title == "Flag2").CheckedState);
+    }
+
+    #endregion Mouse Tests
+
+    [Fact]
+    public void Key_Space_On_Activated_NoneFlag_Does_Nothing ()
+    {
+        FlagSelector selector = new ();
+        selector.Styles = SelectorStyles.ShowNoneFlag;
+        List<string> options = ["Flag1", "Flag2"];
 
-        checkBox = flagSelector.SubViews.OfType<CheckBox> ().First (cb => (uint)cb.Data == Convert.ToUInt32 (FlagSelectorStyles.ShowValueEdit));
+        selector.Labels = options;
+        selector.Layout ();
+
+        CheckBox checkBox = selector.SubViews.OfType<CheckBox> ().First (cb => cb.Title == "Flag1");
+        Assert.Null (selector.Value);
         Assert.Equal (CheckState.UnChecked, checkBox.CheckedState);
+        selector.Value = 0;
+        Assert.Equal (0, selector.Value);
+        Assert.Equal (CheckState.Checked, checkBox.CheckedState);
+
+        checkBox.NewKeyDownEvent (Key.Space);
+
+        Assert.Equal (0, selector.Value);
+        Assert.Equal (CheckState.Checked, checkBox.CheckedState);
+        Assert.Equal (CheckState.UnChecked, selector.SubViews.OfType<CheckBox> ().First (cb => cb.Title == "Flag2").CheckedState);
     }
 
+
     [Fact]
-    public void GenericValueChanged_Event_ShouldBeRaised ()
+    public void Key_Space_On_NotActivated_NoneFlag_Activates ()
     {
-        var flagSelector = new FlagSelector<FlagSelectorStyles> ();
+        FlagSelector selector = new ();
+        selector.Styles = SelectorStyles.ShowNoneFlag;
 
-        flagSelector.SetFlagNames (f => f.ToString ());
-        bool eventRaised = false;
-        flagSelector.ValueChanged += (sender, args) => eventRaised = true;
+        List<string> options = ["Flag1", "Flag2"];
 
-        flagSelector.Value = FlagSelectorStyles.ShowNone;
+        selector.Labels = options;
+        selector.Layout ();
 
-        Assert.True (eventRaised);
+        CheckBox checkBox = selector.SubViews.OfType<CheckBox> ().First (cb => cb.Title == "Flag1");
+        Assert.Null (selector.Value);
+        Assert.Equal (CheckState.UnChecked, checkBox.CheckedState);
+
+        checkBox.NewKeyDownEvent (Key.Space);
+
+        Assert.Equal (0, selector.Value);
+        Assert.Equal (CheckState.Checked, checkBox.CheckedState);
+        Assert.Equal (CheckState.UnChecked, selector.SubViews.OfType<CheckBox> ().First (cb => cb.Title == "Flag2").CheckedState);
+    }
+
+    #region FlagSelector-Specific Tests (Non-Base Class Functionality)
+
+    [Fact]
+    public void Value_MultipleFlags_CombinesCorrectly ()
+    {
+        var selector = new FlagSelector ();
+        selector.Values = [1, 2, 4];
+        selector.Labels = ["Flag1", "Flag2", "Flag3"];
+
+        selector.Value = 1 | 4; // Flags 1 and 3
+
+        CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
+        Assert.Equal (CheckState.Checked, checkBoxes [0].CheckedState); // Flag 1
+        Assert.Equal (CheckState.UnChecked, checkBoxes [1].CheckedState); // Flag 2
+        Assert.Equal (CheckState.Checked, checkBoxes [2].CheckedState); // Flag 3
+    }
+
+    [Fact]
+    public void Value_AllFlags_UpdatesCorrectly ()
+    {
+        var selector = new FlagSelector ();
+        selector.Values = [1, 2, 4];
+        selector.Labels = ["Flag1", "Flag2", "Flag3"];
+
+        selector.Value = 1 | 2 | 4; // All flags
+
+        CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
+        Assert.True (checkBoxes.All (cb => cb.CheckedState == CheckState.Checked));
+    }
+
+    [Fact]
+    public void Value_ToggleFlag_AddsAndRemovesCorrectly ()
+    {
+        var selector = new FlagSelector ();
+        selector.Values = [1, 2, 4];
+        selector.Labels = ["Flag1", "Flag2", "Flag3"];
+
+        selector.Value = 1; // Start with Flag 1
+        Assert.Equal (1, selector.Value);
+
+        // Toggle checkbox to add Flag 2
+        CheckBox flag2Checkbox = selector.SubViews.OfType<CheckBox> ().First (cb => (int)cb.Data! == 2);
+        flag2Checkbox.CheckedState = CheckState.Checked;
+
+        Assert.Equal (1 | 2, selector.Value);
+
+        // Toggle checkbox to remove Flag 1
+        CheckBox flag1Checkbox = selector.SubViews.OfType<CheckBox> ().First (cb => (int)cb.Data! == 1);
+        flag1Checkbox.CheckedState = CheckState.UnChecked;
+
+        Assert.Equal (2, selector.Value);
+    }
+
+    [Fact]
+    public void Value_SetToZero_ChecksNoneFlag ()
+    {
+        var selector = new FlagSelector ();
+        selector.Styles = SelectorStyles.ShowNoneFlag;
+        selector.Values = [1, 2];
+        selector.Labels = ["Flag1", "Flag2"];
+
+        selector.Value = 0;
+
+        CheckBox? noneCheckBox = selector.SubViews.OfType<CheckBox> ().FirstOrDefault (cb => cb.Title == "None");
+        Assert.NotNull (noneCheckBox);
+        Assert.Equal (CheckState.Checked, noneCheckBox.CheckedState);
+
+        // All other flags should be unchecked
+        Assert.True (selector.SubViews.OfType<CheckBox> ()
+                             .Where (cb => (int)cb.Data! != 0)
+                             .All (cb => cb.CheckedState == CheckState.UnChecked));
+    }
+
+    [Fact]
+    public void Value_SetToNull_UnchecksAllIncludingNone ()
+    {
+        var selector = new FlagSelector ();
+        selector.Styles = SelectorStyles.ShowNoneFlag;
+        selector.Values = [1, 2];
+        selector.Labels = ["Flag1", "Flag2"];
+        selector.Value = 1;
+
+        selector.Value = null;
+
+        Assert.True (selector.SubViews.OfType<CheckBox> ()
+                             .All (cb => cb.CheckedState == CheckState.UnChecked));
+    }
+
+    [Fact]
+    public void ToggleNoneFlag_UnchecksAllOtherFlags ()
+    {
+        var selector = new FlagSelector ();
+        selector.Styles = SelectorStyles.ShowNoneFlag;
+        selector.Values = [1, 2];
+        selector.Labels = ["Flag1", "Flag2"];
+        selector.Value = 1 | 2; // Start with all flags set
+
+        CheckBox? noneCheckBox = selector.SubViews.OfType<CheckBox> ().FirstOrDefault (cb => cb.Title == "None");
+        Assert.NotNull (noneCheckBox);
+
+        noneCheckBox.CheckedState = CheckState.Checked;
+
+        Assert.Equal (0, selector.Value);
+        Assert.True (selector.SubViews.OfType<CheckBox> ()
+                             .Where (cb => (int)cb.Data! != 0)
+                             .All (cb => cb.CheckedState == CheckState.UnChecked));
+    }
+
+    [Fact]
+    public void ToggleAnyFlag_UnchecksNoneFlag ()
+    {
+        var selector = new FlagSelector ();
+        selector.Styles = SelectorStyles.ShowNoneFlag;
+        selector.Values = [1, 2];
+        selector.Labels = ["Flag1", "Flag2"];
+        selector.Value = 0; // Start with None
+
+        CheckBox? noneCheckBox = selector.SubViews.OfType<CheckBox> ().FirstOrDefault (cb => cb.Title == "None");
+        Assert.NotNull (noneCheckBox);
+        Assert.Equal (CheckState.Checked, noneCheckBox.CheckedState);
+
+        // Toggle a flag
+        CheckBox flag1CheckBox = selector.SubViews.OfType<CheckBox> ().First (cb => (int)cb.Data! == 1);
+        flag1CheckBox.CheckedState = CheckState.Checked;
+
+        Assert.Equal (1, selector.Value);
+        Assert.Equal (CheckState.UnChecked, noneCheckBox.CheckedState);
+    }
+
+    [Fact]
+    public void NoneFlag_WithoutShowNoneFlag_IsNotCreated ()
+    {
+        var selector = new FlagSelector ();
+        selector.Styles = SelectorStyles.None; // No ShowNoneFlag
+        selector.Values = [1, 2];
+        selector.Labels = ["Flag1", "Flag2"];
+
+        CheckBox? noneCheckBox = selector.SubViews.OfType<CheckBox> ().FirstOrDefault (cb => cb.Title == "None");
+        Assert.Null (noneCheckBox);
+        Assert.Equal (2, selector.SubViews.OfType<CheckBox> ().Count ());
+    }
+
+    [Fact]
+    public void NoneFlag_AlreadyInValues_IsNotDuplicated ()
+    {
+        var selector = new FlagSelector ();
+        selector.Styles = SelectorStyles.ShowNoneFlag;
+        selector.Values = [0, 1, 2]; // 0 already included
+        selector.Labels = ["None", "Flag1", "Flag2"];
+
+        // Should only have one "None" checkbox
+        Assert.Equal (1, selector.SubViews.OfType<CheckBox> ().Count (cb => (int)cb.Data! == 0));
+    }
+
+    [Fact]
+    public void Mouse_DoubleClick_TogglesAndAccepts ()
+    {
+        var selector = new FlagSelector { DoubleClickAccepts = true };
+        selector.Values = [1, 2];
+        selector.Labels = ["Flag1", "Flag2"];
+        selector.Layout ();
+
+        var acceptCount = 0;
+        selector.Accepting += (s, e) => acceptCount++;
+
+        CheckBox checkBox = selector.SubViews.OfType<CheckBox> ().First ();
+        // When Values is set, SelectorBase.Value defaults to the first value (1 in this case)
+        // So the first checkbox (Data == 1) will be checked
+        Assert.Equal (CheckState.Checked, checkBox.CheckedState); // FIXED: Was UnChecked
+        Assert.Equal (1, selector.Value); // Verify Value is set to first value
+
+        checkBox.NewMouseEvent (new () { Position = Point.Empty, Flags = MouseFlags.Button1Clicked });
+        checkBox.NewMouseEvent (new () { Position = Point.Empty, Flags = MouseFlags.Button1DoubleClicked });
+
+        Assert.Equal (1, acceptCount);
+        // After double-clicking on an already-checked flag checkbox, it should still be checked (flags don't uncheck on double-click in FlagSelector)
+        Assert.Equal (CheckState.Checked, checkBox.CheckedState);
+    }
+
+    [Fact]
+    public void UpdateChecked_PreventsConcurrentModification ()
+    {
+        var selector = new FlagSelector ();
+        selector.Values = [1, 2, 4];
+        selector.Labels = ["Flag1", "Flag2", "Flag3"];
+        selector.Value = 1;
+
+        // This should not cause recursion or errors
+        var exception = Record.Exception (() =>
+        {
+            CheckBox checkBox = selector.SubViews.OfType<CheckBox> ().First (cb => (int)cb.Data! == 2);
+            checkBox.CheckedState = CheckState.Checked; // This triggers UpdateChecked internally
+        });
+
+        Assert.Null (exception);
+        Assert.Equal (1 | 2, selector.Value);
+    }
+
+    #endregion
+
+    #region FlagSelector<T> Specific Tests
+
+    [Fact]
+    public void Generic_Value_SetWithEnum_UpdatesCorrectly ()
+    {
+        var selector = new FlagSelector<SelectorStyles> ();
+
+        selector.Value = SelectorStyles.ShowNoneFlag | SelectorStyles.ShowValue;
+
+        Assert.True ((selector.Value & SelectorStyles.ShowNoneFlag) == SelectorStyles.ShowNoneFlag);
+        Assert.True ((selector.Value & SelectorStyles.ShowValue) == SelectorStyles.ShowValue);
+        // SelectorStyles.None is 0, so checking (value & 0) == 0 will always be true
+        // What we actually want to check is that the value is NOT zero (i.e., not None)
+        Assert.True (selector.Value != SelectorStyles.None);
+    }
+
+    [Fact]
+    public void Generic_AutomaticallyPopulatesFromEnum ()
+    {
+        var selector = new FlagSelector<SelectorStyles> ();
+
+        // Should auto-populate from enum
+        Assert.NotNull (selector.Values);
+        Assert.NotNull (selector.Labels);
+        Assert.Equal (Enum.GetValues<SelectorStyles> ().Length, selector.Values.Count);
     }
+
+    [Fact]
+    public void Generic_NullValue_UnchecksAll ()
+    {
+        var selector = new FlagSelector<SelectorStyles> ();
+        selector.Value = SelectorStyles.ShowNoneFlag;
+
+        selector.Value = null;
+
+        Assert.Null (selector.Value);
+        Assert.True (selector.SubViews.OfType<CheckBox> ()
+                             .All (cb => cb.CheckedState == CheckState.UnChecked));
+    }
+
+    [Fact]
+    public void Generic_AllFlagsValue_ChecksAll ()
+    {
+        var selector = new FlagSelector<SelectorStyles> ();
+
+        selector.Value = SelectorStyles.All;
+
+        // All non-None flags should be checked
+        Assert.True (selector.SubViews.OfType<CheckBox> ()
+                             .Where (cb => (int)cb.Data! != 0)
+                             .All (cb => cb.CheckedState == CheckState.Checked));
+    }
+
+    #endregion
 }
+
+

+ 447 - 0
Tests/UnitTestsParallelizable/Views/OptionSelectorTests.cs

@@ -0,0 +1,447 @@
+namespace UnitTests_Parallelizable.ViewsTests;
+
+public class OptionSelectorTests
+{
+    [Fact]
+    public void Initialization_ShouldSetDefaults ()
+    {
+        var optionSelector = new OptionSelector ();
+
+        Assert.True (optionSelector.CanFocus);
+        Assert.Equal (Dim.Auto (DimAutoStyle.Content), optionSelector.Width);
+        Assert.Equal (Dim.Auto (DimAutoStyle.Content), optionSelector.Height);
+        Assert.Equal (Orientation.Vertical, optionSelector.Orientation);
+        Assert.Equal (0, optionSelector.Value);
+        Assert.Null (optionSelector.Labels);
+    }
+
+
+    [Fact]
+    public void Initialization_With_Options_Value_Is_First ()
+    {
+        OptionSelector optionSelector = new OptionSelector ();
+        List<string> options = ["Option1", "Option2"];
+
+        optionSelector.Labels = options;
+        Assert.Equal (0, optionSelector.Value);
+
+        CheckBox checkBox = optionSelector.SubViews.OfType<CheckBox> ().First (cb => cb.Title == "Option1");
+        Assert.Equal (CheckState.Checked, checkBox.CheckedState);
+    }
+
+
+    [Fact]
+    public void SetOptions_ShouldCreateCheckBoxes ()
+    {
+        var optionSelector = new OptionSelector ();
+        List<string> options = new () { "Option1", "Option2", "Option3" };
+
+        optionSelector.Labels = options;
+
+        Assert.Equal (options, optionSelector.Labels);
+        Assert.Equal (options.Count, optionSelector.SubViews.OfType<CheckBox> ().Count ());
+        Assert.Contains (optionSelector.SubViews, sv => sv is CheckBox cb && cb.Title == "Option1");
+        Assert.Contains (optionSelector.SubViews, sv => sv is CheckBox cb && cb.Title == "Option2");
+        Assert.Contains (optionSelector.SubViews, sv => sv is CheckBox cb && cb.Title == "Option3");
+    }
+
+    [Fact]
+    public void Value_Set_ShouldUpdateCheckedState ()
+    {
+        var optionSelector = new OptionSelector ();
+        List<string> options = new () { "Option1", "Option2" };
+
+        optionSelector.Labels = options;
+        optionSelector.Value = 1;
+
+        CheckBox selectedCheckBox = optionSelector.SubViews.OfType<CheckBox> ().First (cb => (int)cb.Data == 1);
+        Assert.Equal (CheckState.Checked, selectedCheckBox.CheckedState);
+
+        CheckBox unselectedCheckBox = optionSelector.SubViews.OfType<CheckBox> ().First (cb => (int)cb.Data == 0);
+        Assert.Equal (CheckState.UnChecked, unselectedCheckBox.CheckedState);
+    }
+
+    [Fact]
+    public void Value_Set_OutOfRange_ShouldThrow ()
+    {
+        var optionSelector = new OptionSelector ();
+        List<string> options = ["Option1", "Option2"];
+
+        optionSelector.Labels = options;
+
+        Assert.Throws<ArgumentOutOfRangeException> (() => optionSelector.Value = -1);
+        Assert.Throws<ArgumentOutOfRangeException> (() => optionSelector.Value = 2);
+    }
+
+    [Fact]
+    public void ValueChanged_Event_ShouldBeRaised ()
+    {
+        var optionSelector = new OptionSelector ();
+        List<string> options = new () { "Option1", "Option2" };
+
+        optionSelector.Labels = options;
+        var eventRaised = false;
+        optionSelector.ValueChanged += (sender, args) => eventRaised = true;
+
+        optionSelector.Value = 1;
+
+        Assert.True (eventRaised);
+    }
+
+    [Fact]
+    public void AssignHotKeys_ShouldAssignUniqueHotKeys ()
+    {
+        var optionSelector = new OptionSelector
+        {
+            AssignHotKeys = true
+        };
+        List<string> options = new () { "Option1", "Option2" };
+
+        optionSelector.Labels = options;
+
+        List<CheckBox> checkBoxes = optionSelector.SubViews.OfType<CheckBox> ().ToList ();
+        Assert.Contains ('_', checkBoxes [0].Title);
+        Assert.Contains ('_', checkBoxes [1].Title);
+    }
+
+    [Fact]
+    public void Orientation_Set_ShouldUpdateLayout ()
+    {
+        var optionSelector = new OptionSelector ();
+        List<string> options = new () { "Option1", "Option2" };
+
+        optionSelector.Labels = options;
+        optionSelector.Orientation = Orientation.Horizontal;
+
+        foreach (CheckBox checkBox in optionSelector.SubViews.OfType<CheckBox> ())
+        {
+            Assert.Equal (0, checkBox.Y);
+        }
+    }
+
+    [Fact]
+    public void HotKey_No_Value_Selects_First ()
+    {
+        var superView = new View
+        {
+            CanFocus = true
+        };
+        superView.Add (new View { CanFocus = true });
+
+        var selector = new OptionSelector
+        {
+            HotKey = Key.G.WithAlt,
+            Labels = ["_Left", "_Right", "Cen_tered", "_Justified"]
+        };
+        selector.Value = null;
+
+        superView.Add (selector);
+
+        Assert.False (selector.HasFocus);
+        Assert.Null (selector.Value);
+
+        selector.NewKeyDownEvent (Key.G.WithAlt);
+
+        Assert.Equal (0, selector.Value);
+        Assert.Equal (selector.SubViews.OfType<CheckBox> ().First (), superView.MostFocused);
+    }
+
+    [Fact]
+    public void Accept_Command_Fires_Accept ()
+    {
+        var optionSelector = new OptionSelector ();
+        optionSelector.Labels = new List<string> { "Option1", "Option2" };
+        var accepted = false;
+
+        optionSelector.Accepting += OnAccept;
+        optionSelector.InvokeCommand (Command.Accept);
+
+        Assert.True (accepted);
+
+        return;
+
+        void OnAccept (object sender, CommandEventArgs e) { accepted = true; }
+    }
+
+    [Fact]
+    public void Mouse_Click_On_Activated_Does_Nothing ()
+    {
+        OptionSelector optionSelector = new OptionSelector ();
+        List<string> options = ["Option1", "Option2"];
+
+        optionSelector.Labels = options;
+        optionSelector.Layout ();
+
+        CheckBox checkBox = optionSelector.SubViews.OfType<CheckBox> ().First (cb => cb.Title == "Option1");
+        Assert.Equal (0, optionSelector.Value);
+        Assert.Equal (CheckState.Checked, checkBox.CheckedState);
+
+        var mouseEvent = new MouseEventArgs
+        {
+            Position = checkBox.Frame.Location,
+            Flags = MouseFlags.Button1Clicked
+        };
+
+        checkBox.NewMouseEvent (mouseEvent);
+
+        Assert.Equal (0, optionSelector.Value);
+        Assert.Equal (CheckState.Checked, checkBox.CheckedState);
+        Assert.Equal (CheckState.UnChecked, optionSelector.SubViews.OfType<CheckBox> ().First (cb => cb.Title == "Option2").CheckedState);
+    }
+
+
+    [Fact]
+    public void Mouse_Click_On_NotActivated_Activates ()
+    {
+        OptionSelector optionSelector = new OptionSelector ();
+        List<string> options = ["Option1", "Option2"];
+
+        optionSelector.Labels = options;
+        optionSelector.Layout ();
+
+        CheckBox checkBox = optionSelector.SubViews.OfType<CheckBox> ().First (cb => cb.Title == "Option2");
+        Assert.Equal (0, optionSelector.Value);
+        Assert.Equal (CheckState.Checked, optionSelector.SubViews.OfType<CheckBox> ().First (cb => cb.Title == "Option1").CheckedState);
+        Assert.Equal (CheckState.UnChecked, checkBox.CheckedState);
+
+        var mouseEvent = new MouseEventArgs
+        {
+            Position = checkBox.Frame.Location,
+            Flags = MouseFlags.Button1Clicked
+        };
+
+        checkBox.NewMouseEvent (mouseEvent);
+
+        Assert.Equal (1, optionSelector.Value);
+        Assert.Equal (CheckState.Checked, checkBox.CheckedState);
+        Assert.Equal (CheckState.UnChecked, optionSelector.SubViews.OfType<CheckBox> ().First (cb => cb.Title == "Option1").CheckedState);
+    }
+
+
+    [Fact]
+    public void Key_Space_On_Activated_Cycles ()
+    {
+        OptionSelector optionSelector = new OptionSelector ();
+        List<string> options = ["Option1", "Option2"];
+
+        optionSelector.Labels = options;
+        optionSelector.Layout ();
+
+        CheckBox checkBox = optionSelector.SubViews.OfType<CheckBox> ().First (cb => cb.Title == "Option1");
+        Assert.Equal (0, optionSelector.Value);
+        Assert.Equal (CheckState.Checked, checkBox.CheckedState);
+
+        checkBox.NewKeyDownEvent (Key.Space);
+
+        Assert.Equal (1, optionSelector.Value);
+        Assert.Equal (CheckState.UnChecked, checkBox.CheckedState);
+        Assert.Equal (CheckState.Checked, optionSelector.SubViews.OfType<CheckBox> ().First (cb => cb.Title == "Option2").CheckedState);
+    }
+
+
+    [Fact]
+    public void Key_Space_On_NotActivated_Activates ()
+    {
+        OptionSelector optionSelector = new OptionSelector ();
+        List<string> options = ["Option1", "Option2"];
+
+        optionSelector.Labels = options;
+        optionSelector.Layout ();
+
+        CheckBox checkBox = optionSelector.SubViews.OfType<CheckBox> ().First (cb => cb.Title == "Option2");
+        Assert.Equal (0, optionSelector.Value);
+        Assert.Equal (CheckState.Checked, optionSelector.SubViews.OfType<CheckBox> ().First (cb => cb.Title == "Option1").CheckedState);
+        Assert.Equal (CheckState.UnChecked, checkBox.CheckedState);
+
+        checkBox.NewKeyDownEvent (Key.Space);
+
+        Assert.Equal (1, optionSelector.Value);
+        Assert.Equal (CheckState.Checked, checkBox.CheckedState);
+        Assert.Equal (CheckState.UnChecked, optionSelector.SubViews.OfType<CheckBox> ().First (cb => cb.Title == "Option1").CheckedState);
+    }
+    [Fact]
+    public void Values_ShouldUseOptions_WhenValuesIsNull ()
+    {
+        var optionSelector = new OptionSelector ();
+        Assert.Null (optionSelector.Values); // Initially null
+
+        List<string> options = ["Option1", "Option2", "Option3"];
+
+        optionSelector.Labels = options;
+
+        IReadOnlyList<int> values = optionSelector.Values;
+
+        Assert.NotNull (values);
+        Assert.Equal (Enumerable.Range (0, options.Count).ToList (), values);
+    }
+
+    [Fact]
+    public void Values_NonSequential_ShouldWorkCorrectly ()
+    {
+        // Arrange
+        OptionSelector optionSelector = new ();
+        List<string> options = new () { "Option _1", "Option _2", "Option _3" };
+        List<int> values = new () { 0, 1, 5 };
+
+        optionSelector.Labels = options;
+        optionSelector.Values = values;
+
+        // Act & Assert
+        Assert.Equal (values, optionSelector.Values);
+        Assert.Equal (options, optionSelector.Labels);
+
+        // Verify that the Value property updates correctly
+        optionSelector.Value = 5;
+        Assert.Equal (5, optionSelector.Value);
+
+        // Verify that the CheckBox states align with the non-sequential Values
+        CheckBox selectedCheckBox = optionSelector.SubViews.OfType<CheckBox> ()
+            .First (cb => (int)cb.Data == 5);
+        Assert.Equal (CheckState.Checked, selectedCheckBox.CheckedState);
+
+        CheckBox unselectedCheckBox = optionSelector.SubViews.OfType<CheckBox> ()
+            .First (cb => (int)cb.Data! == 0); // Index 0 corresponds to value 0
+        Assert.Equal (CheckState.UnChecked, unselectedCheckBox.CheckedState);
+    }
+
+
+    [Fact]
+    public void Item_HotKey_Null_Value_Changes_Value_And_SetsFocus ()
+    {
+        var superView = new View
+        {
+            CanFocus = true
+        };
+        superView.Add (new View { Id = "otherView", CanFocus = true });
+        var selector = new OptionSelector ();
+        selector.Labels = ["_One", "_Two"];
+        superView.Add (selector);
+        superView.SetFocus ();
+
+        Assert.False (selector.HasFocus);
+        Assert.Equal (0, selector.Value);
+        selector.Value = null;
+        Assert.False (selector.HasFocus);
+
+        selector.NewKeyDownEvent (Key.T);
+
+        Assert.True (selector.HasFocus);
+        Assert.Equal (1, selector.Value);
+    }
+
+    [Fact]
+    public void Cursor_Get_ReturnsCorrectIndex ()
+    {
+        var optionSelector = new OptionSelector ();
+        List<string> options = ["Option1", "Option2", "Option3"];
+
+        optionSelector.Labels = options;
+        optionSelector.Layout ();
+
+        // Set focus to second checkbox
+        CheckBox secondCheckBox = optionSelector.SubViews.OfType<CheckBox> ().ToArray () [1];
+        secondCheckBox.SetFocus ();
+
+        Assert.Equal (1, optionSelector.Cursor);
+
+        // Set focus to third checkbox
+        CheckBox thirdCheckBox = optionSelector.SubViews.OfType<CheckBox> ().ToArray () [2];
+        thirdCheckBox.SetFocus ();
+
+        Assert.Equal (2, optionSelector.Cursor);
+    }
+
+    [Fact]
+    public void Cursor_Get_WhenNotFocusable_ReturnsZero ()
+    {
+        var optionSelector = new OptionSelector
+        {
+            CanFocus = false
+        };
+        List<string> options = ["Option1", "Option2", "Option3"];
+
+        optionSelector.Labels = options;
+        optionSelector.Layout ();
+
+        Assert.Equal (0, optionSelector.Cursor);
+    }
+
+    [Fact]
+    public void Cursor_Set_ShouldMoveFocusToCorrectCheckBox ()
+    {
+        var optionSelector = new OptionSelector ();
+        List<string> options = ["Option1", "Option2", "Option3"];
+
+        optionSelector.Labels = options;
+        optionSelector.Layout ();
+
+        // Set cursor to second checkbox
+        optionSelector.Cursor = 1;
+
+        CheckBox [] checkBoxes = optionSelector.SubViews.OfType<CheckBox> ().ToArray ();
+        Assert.True (checkBoxes [1].HasFocus);
+        Assert.Equal (1, optionSelector.Cursor);
+
+        // Set cursor to third checkbox
+        optionSelector.Cursor = 2;
+
+        Assert.True (checkBoxes [2].HasFocus);
+        Assert.Equal (2, optionSelector.Cursor);
+    }
+
+    [Fact]
+    public void Cursor_Set_OutOfRange_ShouldThrow ()
+    {
+        var optionSelector = new OptionSelector ();
+        List<string> options = ["Option1", "Option2", "Option3"];
+
+        optionSelector.Labels = options;
+        optionSelector.Layout ();
+
+        Assert.Throws<ArgumentOutOfRangeException> (() => optionSelector.Cursor = -1);
+        Assert.Throws<ArgumentOutOfRangeException> (() => optionSelector.Cursor = 3);
+    }
+
+    [Fact]
+    public void Cursor_Set_WhenNotFocusable_DoesNothing ()
+    {
+        var optionSelector = new OptionSelector
+        {
+            CanFocus = false
+        };
+        List<string> options = ["Option1", "Option2", "Option3"];
+
+        optionSelector.Labels = options;
+        optionSelector.Layout ();
+
+        // Should not throw
+        optionSelector.Cursor = 1;
+
+        // Verify nothing changed
+        Assert.Equal (0, optionSelector.Cursor);
+        Assert.False (optionSelector is { } && optionSelector.SubViews.OfType<CheckBox> ().Any (cb => cb.HasFocus));
+    }
+
+    [Fact]
+    public void Cursor_DoesNotChangeValue ()
+    {
+        var optionSelector = new OptionSelector ();
+        List<string> options = ["Option1", "Option2", "Option3"];
+
+        optionSelector.Labels = options;
+        optionSelector.Value = 0; // First option is selected
+        optionSelector.Layout ();
+
+        // Move cursor to second checkbox
+        optionSelector.Cursor = 1;
+
+        // Value should not change, only focus moves
+        Assert.Equal (0, optionSelector.Value);
+        Assert.Equal (1, optionSelector.Cursor);
+
+        CheckBox [] checkBoxes = optionSelector.SubViews.OfType<CheckBox> ().ToArray ();
+        Assert.Equal (CheckState.Checked, checkBoxes [0].CheckedState);
+        Assert.Equal (CheckState.UnChecked, checkBoxes [1].CheckedState);
+        Assert.True (checkBoxes [1].HasFocus);
+    }
+}

+ 604 - 0
Tests/UnitTestsParallelizable/Views/SelectorBaseTests.cs

@@ -0,0 +1,604 @@
+#nullable enable
+namespace UnitTests_Parallelizable.ViewsTests;
+
+/// <summary>
+///     Tests for <see cref="SelectorBase"/> functionality that applies to all selector implementations.
+///     These tests use <see cref="OptionSelector"/> as a concrete implementation to test the base class.
+/// </summary>
+public class SelectorBaseTests
+{
+    #region Initialization Tests
+
+    [Fact]
+    public void Constructor_SetsDefaults ()
+    {
+        var selector = new OptionSelector ();
+
+        Assert.True (selector.CanFocus);
+        Assert.Equal (Dim.Auto (DimAutoStyle.Content), selector.Width);
+        Assert.Equal (Dim.Auto (DimAutoStyle.Content), selector.Height);
+        Assert.Equal (Orientation.Vertical, selector.Orientation);
+        Assert.Null (selector.Labels);
+        Assert.Null (selector.Values);
+        Assert.False (selector.AssignHotKeys);
+        Assert.Empty (selector.UsedHotKeys);
+        Assert.Equal (SelectorStyles.None, selector.Styles);
+        Assert.True (selector.DoubleClickAccepts);
+        Assert.Equal (2, selector.HorizontalSpace);
+    }
+
+    #endregion
+
+    #region Value Property Tests
+
+    [Fact]
+    public void Value_Set_ValidValue_UpdatesValue ()
+    {
+        var selector = new OptionSelector ();
+        selector.Labels = ["Option1", "Option2"];
+
+        selector.Value = 1;
+
+        Assert.Equal (1, selector.Value);
+    }
+
+    [Fact]
+    public void Value_Set_InvalidValue_ThrowsArgumentOutOfRangeException ()
+    {
+        var selector = new OptionSelector ();
+        selector.Labels = ["Option1", "Option2"];
+
+        Assert.Throws<ArgumentOutOfRangeException> (() => selector.Value = 5);
+        Assert.Throws<ArgumentOutOfRangeException> (() => selector.Value = -1);
+    }
+
+    [Fact]
+    public void Value_Set_Null_Succeeds ()
+    {
+        var selector = new OptionSelector ();
+        selector.Labels = ["Option1", "Option2"];
+
+        selector.Value = null;
+
+        Assert.Null (selector.Value);
+    }
+
+    [Fact]
+    public void Value_Set_SameValue_DoesNotRaiseEvent ()
+    {
+        var selector = new OptionSelector ();
+        selector.Labels = ["Option1", "Option2"];
+        selector.Value = 1;
+
+        var eventRaisedCount = 0;
+        selector.ValueChanged += (s, e) => eventRaisedCount++;
+
+        selector.Value = 1; // Set to same value
+
+        Assert.Equal (0, eventRaisedCount);
+    }
+
+    [Fact]
+    public void Value_Changed_RaisesValueChangedEvent ()
+    {
+        var selector = new OptionSelector ();
+        selector.Labels = ["Option1", "Option2"];
+
+        int? capturedValue = null;
+        selector.ValueChanged += (s, e) => capturedValue = e.Value;
+
+        selector.Value = 1;
+
+        Assert.Equal (1, capturedValue);
+    }
+
+    #endregion
+
+    #region Values Property Tests
+
+    [Fact]
+    public void Values_Get_WhenNull_ReturnsSequentialValues ()
+    {
+        var selector = new OptionSelector ();
+        selector.Labels = ["Option1", "Option2", "Option3"];
+
+        IReadOnlyList<int>? values = selector.Values;
+
+        Assert.NotNull (values);
+        Assert.Equal (3, values.Count);
+        Assert.Equal ([0, 1, 2], values);
+    }
+
+    [Fact]
+    public void Values_Set_UpdatesValues ()
+    {
+        var selector = new OptionSelector ();
+        selector.Labels = ["Option1", "Option2"];
+
+        selector.Values = [10, 20];
+
+        Assert.Equal ([10, 20], selector.Values);
+    }
+
+    [Fact]
+    public void Values_Set_SetsDefaultValue ()
+    {
+        var selector = new OptionSelector ();
+        selector.Value = null;
+        selector.Labels = ["Option1", "Option2"];
+
+        selector.Values = [10, 20];
+
+        Assert.Equal (10, selector.Value); // Should default to first value
+    }
+
+    #endregion
+
+    #region Labels Property Tests
+
+    [Fact]
+    public void Labels_Set_CreatesSubViews ()
+    {
+        var selector = new OptionSelector ();
+
+        selector.Labels = ["Option1", "Option2"];
+
+        Assert.Equal (2, selector.SubViews.OfType<CheckBox> ().Count ());
+    }
+
+    [Fact]
+    public void Labels_Set_Null_RemovesSubViews ()
+    {
+        var selector = new OptionSelector ();
+        selector.Labels = ["Option1", "Option2"];
+
+        selector.Labels = null;
+
+        Assert.Empty (selector.SubViews.OfType<CheckBox> ());
+    }
+
+    [Fact]
+    public void Labels_Values_CountMismatch_DoesNotCreateSubViews ()
+    {
+        var selector = new OptionSelector ();
+
+        selector.Values = [0, 1, 2];
+        selector.Labels = ["Option1", "Option2"]; // Mismatch
+
+        Assert.Empty (selector.SubViews.OfType<CheckBox> ());
+    }
+
+    #endregion
+
+    #region SetValuesAndLabels Tests
+
+    [Fact]
+    public void SetValuesAndLabels_FromEnum_SetsValuesAndLabels ()
+    {
+        var selector = new OptionSelector ();
+
+        selector.SetValuesAndLabels<SelectorStyles> ();
+
+        Assert.NotNull (selector.Values);
+        Assert.NotNull (selector.Labels);
+        Assert.Equal (Enum.GetValues<SelectorStyles> ().Length, selector.Values.Count);
+        Assert.Equal (Enum.GetNames<SelectorStyles> (), selector.Labels);
+    }
+
+    [Fact]
+    public void SetValuesAndLabels_SetsCorrectIntegerValues ()
+    {
+        var selector = new OptionSelector ();
+
+        selector.SetValuesAndLabels<SelectorStyles> ();
+
+        // Verify values match enum integer values
+        var expectedValues = Enum.GetValues<SelectorStyles> ().Select (e => (int)e).ToList ();
+        Assert.Equal (expectedValues, selector.Values);
+    }
+
+    #endregion
+
+    #region Styles Property Tests
+
+    [Fact]
+    public void Styles_Set_None_NoExtraSubViews ()
+    {
+        var selector = new OptionSelector ();
+        selector.Labels = ["Option1", "Option2"];
+
+        selector.Styles = SelectorStyles.None;
+
+        Assert.Equal (2, selector.SubViews.Count);
+        Assert.Null (selector.SubViews.FirstOrDefault (v => v.Id == "valueField"));
+    }
+
+    [Fact]
+    public void Styles_Set_ShowValue_AddsValueField ()
+    {
+        var selector = new OptionSelector ();
+        selector.Labels = ["Option1", "Option2"];
+
+        selector.Styles = SelectorStyles.ShowValue;
+
+        View? valueField = selector.SubViews.FirstOrDefault (v => v.Id == "valueField");
+        Assert.NotNull (valueField);
+        Assert.IsType<TextField> (valueField);
+    }
+
+    [Fact]
+    public void Styles_Set_ShowValue_ValueFieldDisplaysCurrentValue ()
+    {
+        var selector = new OptionSelector ();
+        selector.Labels = ["Option1", "Option2"];
+        selector.Value = 1;
+
+        selector.Styles = SelectorStyles.ShowValue;
+
+        var valueField = (TextField?)selector.SubViews.FirstOrDefault (v => v.Id == "valueField");
+        Assert.NotNull (valueField);
+        Assert.Equal ("1", valueField.Text);
+    }
+
+    [Fact]
+    public void Styles_Set_ShowValue_ValueFieldUpdatesOnValueChange ()
+    {
+        var selector = new OptionSelector ();
+        selector.Labels = ["Option1", "Option2"];
+        selector.Styles = SelectorStyles.ShowValue;
+
+        selector.Value = 1;
+
+        var valueField = (TextField?)selector.SubViews.FirstOrDefault (v => v.Id == "valueField");
+        Assert.NotNull (valueField);
+        Assert.Equal ("1", valueField.Text);
+    }
+
+    [Fact]
+    public void Styles_Set_SameValue_DoesNotRecreateSubViews ()
+    {
+        var selector = new OptionSelector ();
+        selector.Labels = ["Option1", "Option2"];
+        selector.Styles = SelectorStyles.ShowValue;
+
+        CheckBox firstCheckBox = selector.SubViews.OfType<CheckBox> ().First ();
+
+        selector.Styles = SelectorStyles.ShowValue; // Set to same value
+
+        // Should be the same instance
+        Assert.Same (firstCheckBox, selector.SubViews.OfType<CheckBox> ().First ());
+    }
+
+    #endregion
+
+    #region AssignHotKeys Tests
+
+    [Fact]
+    public void AssignHotKeys_True_AssignsHotKeysToCheckBoxes ()
+    {
+        var selector = new OptionSelector { AssignHotKeys = true };
+
+        selector.Labels = ["Option1", "Option2"];
+
+        CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
+        Assert.NotEqual (Key.Empty, checkBoxes [0].HotKey);
+        Assert.NotEqual (Key.Empty, checkBoxes [1].HotKey);
+    }
+
+    [Fact]
+    public void AssignHotKeys_True_AddsHotKeySpecifierToTitles ()
+    {
+        var selector = new OptionSelector { AssignHotKeys = true };
+
+        selector.Labels = ["Option1", "Option2"];
+
+        CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
+        Assert.Contains ('_', checkBoxes [0].Title);
+        Assert.Contains ('_', checkBoxes [1].Title);
+    }
+
+    [Fact]
+    public void AssignHotKeys_True_AssignsUniqueHotKeys ()
+    {
+        var selector = new OptionSelector { AssignHotKeys = true };
+
+        selector.Labels = ["Option1", "Option2", "Option3"];
+
+        CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
+        var hotKeys = checkBoxes.Select (cb => cb.HotKey).ToList ();
+        Assert.Equal (3, hotKeys.Distinct ().Count ()); // All unique
+    }
+
+    [Fact]
+    public void AssignHotKeys_False_DoesNotAssignHotKeys ()
+    {
+        var selector = new OptionSelector { AssignHotKeys = false };
+
+        selector.Labels = ["Option1", "Option2"];
+
+        CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
+        Assert.Equal (Key.Empty, checkBoxes [0].HotKey);
+        Assert.Equal (Key.Empty, checkBoxes [1].HotKey);
+    }
+
+    [Fact]
+    public void AssignHotKeys_PreservesExistingHotKeys ()
+    {
+        var selector = new OptionSelector { AssignHotKeys = true };
+
+        selector.Labels = ["_Alt Option", "Option2"];
+
+        CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
+        // Should use 'A' from "_Alt"
+        Assert.Equal (Key.A, checkBoxes [0].HotKey);
+    }
+
+    #endregion
+
+    #region UsedHotKeys Tests
+
+    [Fact]
+    public void UsedHotKeys_SkipsMarkedKeys ()
+    {
+        var selector = new OptionSelector { AssignHotKeys = true };
+
+        selector.UsedHotKeys.Add (Key.O); // Mark 'O' as used
+        selector.Labels = ["Option1", "Option2"];
+
+        CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
+        // Should skip 'O' and use next available character
+        Assert.NotEqual (Key.O, checkBoxes [0].HotKey);
+    }
+
+    [Fact]
+    public void UsedHotKeys_PopulatedWhenHotKeysAssigned ()
+    {
+        var selector = new OptionSelector { AssignHotKeys = true };
+
+        selector.Labels = ["Option1", "Option2"];
+
+        // UsedHotKeys should contain the assigned hotkeys
+        Assert.NotEmpty (selector.UsedHotKeys);
+        CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
+
+        foreach (CheckBox cb in checkBoxes)
+        {
+            Assert.Contains (cb.HotKey, selector.UsedHotKeys);
+        }
+    }
+
+    #endregion
+
+    #region Orientation Tests
+
+    [Fact]
+    public void Orientation_Vertical_CheckBoxesStackedVertically ()
+    {
+        var selector = new OptionSelector ();
+        selector.Labels = ["Option1", "Option2"];
+        selector.Orientation = Orientation.Vertical;
+        selector.Layout ();
+
+        CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
+        Assert.Equal (0, checkBoxes [0].Frame.Y);
+        Assert.True (checkBoxes [1].Frame.Y > checkBoxes [0].Frame.Y);
+    }
+
+    [Fact]
+    public void Orientation_Horizontal_CheckBoxesArrangedHorizontally ()
+    {
+        var selector = new OptionSelector ();
+        selector.Labels = ["Option1", "Option2"];
+        selector.Orientation = Orientation.Horizontal;
+        selector.Layout ();
+
+        CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
+        Assert.Equal (0, checkBoxes [0].Frame.Y);
+        Assert.Equal (0, checkBoxes [1].Frame.Y);
+        Assert.True (checkBoxes [1].Frame.X > checkBoxes [0].Frame.X);
+    }
+
+    [Fact]
+    public void Orientation_Change_TriggersLayout ()
+    {
+        var selector = new OptionSelector ();
+        selector.Labels = ["Option1", "Option2"];
+        selector.Layout ();
+
+        CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
+        int originalYDiff = checkBoxes [1].Frame.Y - checkBoxes [0].Frame.Y;
+
+        selector.Orientation = Orientation.Horizontal;
+        selector.Layout ();
+
+        int newYDiff = checkBoxes [1].Frame.Y - checkBoxes [0].Frame.Y;
+        Assert.NotEqual (originalYDiff, newYDiff);
+        Assert.Equal (0, newYDiff); // Both should be at Y=0 now
+    }
+
+    #endregion
+
+    #region HorizontalSpace Tests
+
+    [Fact]
+    public void HorizontalSpace_Default_Is2 ()
+    {
+        var selector = new OptionSelector ();
+
+        Assert.Equal (2, selector.HorizontalSpace);
+    }
+
+    [Fact]
+    public void HorizontalSpace_Set_UpdatesSpacing ()
+    {
+        var selector = new OptionSelector { Orientation = Orientation.Horizontal };
+        selector.Labels = ["Option1", "Option2"];
+        selector.HorizontalSpace = 2;
+        selector.Layout ();
+
+        CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
+        // HorizontalSpace is applied via Margin.Thickness.Right
+        int spacing2 = checkBoxes [0].Margin!.Thickness.Right;
+
+        selector.HorizontalSpace = 5;
+        selector.Layout ();
+
+        int spacing5 = checkBoxes [0].Margin!.Thickness.Right;
+        Assert.True (spacing5 > spacing2);
+        Assert.Equal (2, spacing2);
+        Assert.Equal (5, spacing5);
+    }
+
+    [Fact]
+    public void HorizontalSpace_OnlyAppliesToHorizontalOrientation ()
+    {
+        var selector = new OptionSelector { Orientation = Orientation.Vertical };
+        selector.Labels = ["Option1", "Option2"];
+        selector.HorizontalSpace = 10;
+        selector.Layout ();
+
+        CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
+        // In vertical mode, checkboxes should be at same X
+        Assert.Equal (checkBoxes [0].Frame.X, checkBoxes [1].Frame.X);
+    }
+
+    #endregion
+
+    #region DoubleClickAccepts Tests
+
+    [Fact]
+    public void DoubleClickAccepts_Default_IsTrue ()
+    {
+        var selector = new OptionSelector ();
+
+        Assert.True (selector.DoubleClickAccepts);
+    }
+
+    [Fact]
+    public void DoubleClickAccepts_True_AcceptOnDoubleClick ()
+    {
+        var selector = new OptionSelector { DoubleClickAccepts = true };
+        selector.Labels = ["Option1", "Option2"];
+        selector.Layout ();
+
+        var acceptCount = 0;
+        selector.Accepting += (s, e) => acceptCount++;
+
+        CheckBox checkBox = selector.SubViews.OfType<CheckBox> ().First ();
+        checkBox.NewMouseEvent (new () { Position = Point.Empty, Flags = MouseFlags.Button1Clicked });
+        checkBox.NewMouseEvent (new () { Position = Point.Empty, Flags = MouseFlags.Button1DoubleClicked });
+
+        Assert.Equal (1, acceptCount);
+    }
+
+    [Fact]
+    public void DoubleClickAccepts_False_DoesNotAcceptOnDoubleClick ()
+    {
+        var selector = new OptionSelector { DoubleClickAccepts = false };
+        selector.Labels = ["Option1", "Option2"];
+        selector.Layout ();
+
+        var acceptCount = 0;
+        selector.Accepting += (s, e) => acceptCount++;
+
+        CheckBox checkBox = selector.SubViews.OfType<CheckBox> ().First ();
+        checkBox.NewMouseEvent (new () { Position = Point.Empty, Flags = MouseFlags.Button1Clicked });
+        checkBox.NewMouseEvent (new () { Position = Point.Empty, Flags = MouseFlags.Button1DoubleClicked });
+
+        Assert.Equal (0, acceptCount);
+    }
+
+    #endregion
+
+    #region CreateSubViews Tests
+
+    [Fact]
+    public void CreateSubViews_RemovesOldSubViewsAndCreatesNew ()
+    {
+        var selector = new OptionSelector ();
+        selector.Labels = ["Option1", "Option2"];
+
+        int oldCount = selector.SubViews.Count;
+
+        selector.Labels = ["New1", "New2", "New3"];
+
+        Assert.NotEqual (oldCount, selector.SubViews.Count);
+        Assert.Equal (3, selector.SubViews.OfType<CheckBox> ().Count ());
+        Assert.Contains (selector.SubViews.OfType<CheckBox> (), cb => cb.Title == "New1");
+    }
+
+    [Fact]
+    public void CreateSubViews_SetsCheckBoxProperties ()
+    {
+        var selector = new OptionSelector ();
+
+        selector.Labels = ["Test Option"];
+        selector.Values = [42];
+
+        CheckBox checkBox = selector.SubViews.OfType<CheckBox> ().First ();
+        Assert.Equal ("Test Option", checkBox.Title);
+        Assert.Equal ("Test Option", checkBox.Id);
+        Assert.Equal (42, checkBox.Data);
+        Assert.True (checkBox.CanFocus);
+    }
+
+    #endregion
+
+    #region HotKey Command Tests
+
+    [Fact]
+    public void HotKey_Command_DoesNotFireAccept ()
+    {
+        var selector = new OptionSelector ();
+        selector.Labels = ["Option1", "Option2"];
+
+        var acceptCount = 0;
+        selector.Accepting += (s, e) => acceptCount++;
+
+        selector.InvokeCommand (Command.HotKey);
+
+        Assert.Equal (0, acceptCount);
+    }
+
+    [Fact]
+    public void Accept_Command_FiresAccept ()
+    {
+        var selector = new OptionSelector ();
+        selector.Labels = ["Option1", "Option2"];
+
+        var acceptCount = 0;
+        selector.Accepting += (s, e) => acceptCount++;
+
+        selector.InvokeCommand (Command.Accept);
+
+        Assert.Equal (1, acceptCount);
+    }
+
+    #endregion
+
+    #region Edge Cases
+
+    [Fact]
+    public void EmptyLabels_CreatesNoSubViews ()
+    {
+        var selector = new OptionSelector ();
+
+        selector.Labels = [];
+
+        Assert.Empty (selector.SubViews);
+    }
+
+    [Fact]
+    public void Value_WithNoLabels_CanBeSet ()
+    {
+        var selector = new OptionSelector ();
+
+        // This should work even without labels
+        var exception = Record.Exception (() => selector.Value = null);
+
+        Assert.Null (exception);
+        Assert.Null (selector.Value);
+    }
+
+    #endregion
+}

+ 1 - 1
docfx/docs/command.md

@@ -169,7 +169,7 @@ These concepts are opinionated, reflecting Terminal.Gui’s view that most UI in
         return commandContext?.Command == Command.HotKey ? cancelled : cancelled is false;
     }
     ```
-  - **RadioGroup**: Selecting a radio button raises `Selecting` to update the selected option.
+  - **OptionSelector**: Selecting an OpitonSelector option raises `Selecting` to update the selected option.
   - **Menuv2** and **MenuBarv2**: Selecting a `MenuItemv2` (e.g., via mouse enter or arrow keys) sets focus, tracked by `SelectedMenuItem` and raising `SelectedMenuItemChanged`:
     ```csharp
     protected override void OnFocusedChanged(View? previousFocused, View? focused)

+ 1 - 1
docfx/docs/navigation.md

@@ -443,7 +443,7 @@ The following table summarizes how built-in views respond to various input metho
 | **Label** | 1 | Yes | No | 1 | OnSelect | OnAccept | FocusNext | Focus | - | FocusNext | No |
 | **Button** | 1 | No | Yes | 1 | OnSelect | Focus+OnAccept | Focus+OnAccept | HotKey | - | Select | No |
 | **CheckBox** | 3 | No | No | 1 | OnSelect+Advance | OnAccept | OnAccept | Select | - | Select | No |
-| **RadioGroup** | >1 | No | No | 2+ | Advance | SetSelected+OnAccept | Focus+SetSelected | SetFocus+SetCursor | - | SetFocus+SetCursor | No |
+| **OptionSelector** | >1 | No | No | 2+ | Advance | SetValue+OnAccept | Focus+SetValue | SetFocus+SetCursor | - | SetFocus+SetCursor | No |
 | **Slider** | >1 | No | No | 1 | SetFocusedOption | SetFocusedOption+OnAccept | Focus | SetFocus+SetOption | - | SetFocus+SetOption | Yes |
 | **ListView** | >1 | No | No | 1 | MarkUnMarkRow | OpenSelected+OnAccept | OnAccept | SetMark+OnSelectedChanged | OpenSelected+OnAccept | - | No |
 | **TextField** | 1 | No | No | 1 | - | OnAccept | Focus | Focus | SelectAll | ContextMenu | No |

+ 37 - 28
docfx/docs/views.md

@@ -31,17 +31,17 @@ A scrollable map of the Unicode codepoints.
 
 ```text
 0  1  2  3  4  5  6  7  8  9  a  b  c  d
-U+00000_                                         
-U+00001_                                         
+U+00000_ ␀  ␁  ␂  ␃  ␄  ␅  ␆  ␇  ␈  ␉  ␊  ␋  ␌  ␍
+U+00001_ ␐  ␑  ␒  ␓  ␔  ␕  ␖  ␗  ␘  ␙  ␚  ␛  ␜  ␝
 U+00002_    !  "  #  $  %  &  '  (  )  *  +  ,  -░
 U+00003_ 0  1  2  3  4  5  6  7  8  9  :  ;  <  =░
 U+00004_ @  A  B  C  D  E  F  G  H  I  J  K  L  M░
 U+00005_ P  Q  R  S  T  U  V  W  X  Y  Z  [  \  ]░
 U+00006_ `  a  b  c  d  e  f  g  h  i  j  k  l  m░
 U+00007_ p  q  r  s  t  u  v  w  x  y  z  {  |  }░
-U+00008_                                         
-U+00009_                                         
-U+0000a_    ¡  ¢  £  ¤  ¥  ¦  §  ¨  ©  ª  «  ¬  ­
+U+00008_ ⒀  ⒁  ⒂  ⒃  ⒄  ⒅  ⒆  ⒇  ⒈  ⒉  ⒊  ⒋  ⒌  ⒍
+U+00009_ ⒐  ⒑  ⒒  ⒓  ⒔  ⒕  ⒖  ⒗  ⒘  ⒙  ⒚  ⒛  ⒜  ⒝
+U+0000a_    ¡  ¢  £  ¤  ¥  ¦  §  ¨  ©  ª  «  ¬  F
 U+0000b_ °  ±  ²  ³  ´  µ  ¶  ·  ¸  ¹  º  »  ¼  ½░
 U+0000c_ À  Á    à  Ä  Å  Æ  Ç  È  É  Ê  Ë  Ì  Í░
 U+0000d_ Ð  Ñ  Ò  Ó  Ô  Õ  Ö  ×  Ø  Ù  Ú  Û  Ü  Ý░
@@ -49,7 +49,7 @@ U+0000e_ à  á  â  ã  ä  å  æ  ç  è  é  ê  ë  ì  í░
 U+0000f_ ð  ñ  ò  ó  ô  õ  ö  ÷  ø  ù  ú  û  ü  ý░
 U+00010_ Ā  ā  Ă  ă  Ą  ą  Ć  ć  Ĉ  ĉ  Ċ  ċ  Č  č░
 U+00011_ Đ  đ  Ē  ē  Ĕ  ĕ  Ė  ė  Ę  ę  Ě  ě  Ĝ  ĝ▼
-        ◄█████████████████████████████████░░░░░░►
+        ◄████████████████████████████░░░░░░░░░░░►
 ```
 
 ## [CheckBox](~/api/Terminal.Gui.Views.CheckBox.yml)
@@ -102,15 +102,15 @@ Lets the user pick a date from a visual calendar.
 
 ```text
 ┌┤Demo Title├────────────────┐
-│Date:  05/31/2025           │
+│Date:  10/31/2025           │
 │┌───┬───┬───┬───┬───┬───┬──┐│
 ││Sun│Mon│Tue│Wed│Thu│Fri│Sa││
 │├───┼───┼───┼───┼───┼───┼──┤│
-││-  │-  │-  │-  │1  │2  │3 ││
-││4  │5  │6  │7  │8  │9  │10││
-││11 │12 │13 │14 │15 │16 │17││
-││18 │19 │20 │21 │22 │23 │24││
-││25 │26 │27 │28 │29 │30 │31││
+││-  │-  │-  │1  │2  │3  │4 ││
+││5  │6  │7  │8  │9  │10 │11││
+││12 │13 │14 │15 │16 │17 │18││
+││19 │20 │21 │22 │23 │24 │25││
+││26 │27 │28 │29 │30 │31 │- ││
 ││-  │-  │-  │-  │-  │-  │- ││
 │└───┴───┴───┴───┴───┴───┴──┘│
 │           ◄◄  ►►           │
@@ -157,11 +157,11 @@ The base-class for [OpenDialog](~/api/Terminal.Gui.Views.OpenDialog.yml) and [Sa
 ┃│\_exported_templates│               │┃
 ┃│\_site              │               │┃
 ┃│\api                │               │┃
+┃│\apispec            │               │┃
 ┃│\docs               │               │┃
 ┃│\images             │               │┃
-┃│\schemas            │               │┃
-┃│\scripts            │               │┃
-┃⟦ ►► ⟧ Enter Search⟦► OK ◄⟧ ⟦ Cancel ⟧┃
+┃Find                                  ┃
+┃⟦►Tree⟧            ⟦► OK ◄⟧ ⟦ Cancel ⟧┃
 ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
 ```
 
@@ -172,6 +172,7 @@ Provides a user interface for displaying and selecting non-mutually-exclusive fl
 ```text
 ☒ No Style
 ☐ Show None Value Style
+☐ ShowAllFlag
 ☐ Show Value Editor Style
 ☐ All Styles
 0
@@ -307,6 +308,12 @@ Draws a single line using the [LineStyle](~/api/Terminal.Gui.Drawing.LineStyle.y
 ──────────────────────────────────────────────────
 ```
 
+## [LineView](~/api/Terminal.Gui.Views.LineView.yml)
+
+A straight line control either horizontal or vertical
+
+
+
 ## [ListView](~/api/Terminal.Gui.Views.ListView.yml)
 
 Provides a scrollable list of data where each item can be activated to perform an action.
@@ -391,11 +398,11 @@ Provides an interactive [Dialog](~/api/Terminal.Gui.Views.Dialog.yml) for select
 ┃│\_exported_templates│               │┃
 ┃│\_site              │               │┃
 ┃│\api                │               │┃
+┃│\apispec            │               │┃
 ┃│\docs               │               │┃
 ┃│\images             │               │┃
-┃│\schemas            │               │┃
-┃│\scripts            │               │┃
-┃⟦ ►► ⟧ Enter Search⟦► OK ◄⟧ ⟦ Cancel ⟧┃
+┃Find                                  ┃
+┃⟦►Tree⟧            ⟦► OK ◄⟧ ⟦ Cancel ⟧┃
 ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
 ```
 
@@ -404,7 +411,7 @@ Provides an interactive [Dialog](~/api/Terminal.Gui.Views.Dialog.yml) for select
 Provides a user interface for displaying and selecting a single item from a list of options. Each option is represented by a checkbox, but only one can be selected at a time.
 
 ```text
- Option 1
+ Option 1
 ○ Option 2
 ○ Third Option
 ○ Option Quattro
@@ -428,11 +435,7 @@ A Progress Bar view that can indicate progress of an activity visually.
 
 Displays a list of mutually-exclusive items. Each items can have its own hotkey.
 
-```text
-◉ Option 1
-○ Option 2
-○ Option 3
-```
+
 
 ## [SaveDialog](~/api/Terminal.Gui.Views.SaveDialog.yml)
 
@@ -449,11 +452,11 @@ Provides an interactive [Dialog](~/api/Terminal.Gui.Views.Dialog.yml) for select
 ┃│\_exported_templates│               │┃
 ┃│\_site              │               │┃
 ┃│\api                │               │┃
+┃│\apispec            │               │┃
 ┃│\docs               │               │┃
 ┃│\images             │               │┃
-┃│\schemas            │               │┃
-┃│\scripts            │               │┃
-┃⟦ ►► ⟧ Enter Sear⟦► Save ◄⟧ ⟦ Cancel ⟧┃
+┃Find                                  ┃
+┃⟦►Tree⟧          ⟦► Save ◄⟧ ⟦ Cancel ⟧┃
 ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
 ```
 
@@ -628,6 +631,12 @@ TextView provides a fully featured multi-line text
 It supports word wrap and history for undo.
 ```
 
+## [TileView](~/api/Terminal.Gui.Views.TileView.yml)
+
+A [View](~/api/Terminal.Gui.ViewBase.View.yml) consisting of a moveable bar that divides the display area into resizeable [TileView.Tiles](~/api/Terminal.Gui.Views.TileView.Tiles.yml).
+
+
+
 ## [TimeField](~/api/Terminal.Gui.Views.TimeField.yml)
 
 Provides time editing functionality with mouse support
@@ -710,7 +719,7 @@ Provides navigation and a user interface (UI) to collect related data across mul
 ║                                        ║
 ║                                        ║
 ║                                        ║
-────────────────────────────────────────
+────────────────────────────────────────
 ║⟦ Back ⟧                    ⟦► Finish ◄⟧║
 ╚════════════════════════════════════════╝
 ```

+ 1 - 2
docfx/scripts/OutputView/OutputView.cs

@@ -13,7 +13,6 @@ ConfigurationManager.RuntimeConfig = """
                                              {   
                                                  "Default": {
                                                      "Window.DefaultShadow": "None",
-                                                     "CheckBox.DefaultHighlightStyle": "None",
                                                      "Dialog.DefaultShadow": "None",
                                                      "Button.DefaultShadow": "None",
                                                      "Menuv2.DefaultBorderStyle": "Single"
@@ -61,7 +60,7 @@ if (string.IsNullOrEmpty (viewName))
 ViewDemoWindow.ViewName = viewName;
 
 // Force 16 colors and end after first iteration
-Application.EndAfterFirstIteration = true;
+Application.StopAfterFirstIteration = true;
 
 var demoWindow = Application.Run<ViewDemoWindow> ();
 string? output = demoWindow.Output?.Trim ();