فهرست منبع

Fixes #2926 - Refactor KeyEvent and KeyEventEventArgs to simplify (#2927)

* Adds basic MainLoop unit tests

* Remove WinChange action from Curses

* Remove WinChange action from Curses

* Remove ProcessInput action from Windows MainLoop

* Simplified MainLoop/ConsoleDriver by making MainLoop internal and moving impt fns to Application

* Modernized Terminal resize events

* Modernized Terminal resize events

* Removed un used property

* for _isWindowsTerminal devenv->wininit; not sure what changed

* Modernized mouse/keyboard events (Action->EventHandler)

* Updated OnMouseEvent API docs

* Using WT_SESSION to detect WT

* removes hacky GetParentProcess

* Updates to fix #2634 (clear last line)

* removes hacky GetParentProcess2

* Addressed mac resize issue

* Addressed mac resize issue

* Removes ConsoleDriver.PrepareToRun, has Init return MainLoop

* Removes unneeded Attribute methods

* Removed GetProcesssName

* Removed GetProcesssName

* Refactored KeyEvent and KeyEventEventArgs into a single class

* Revert "Refactored KeyEvent and KeyEventEventArgs into a single class"

This reverts commit 88a00658dbcb53306d56af1b766594c0eea10b2c.

* Fixed key repeat issue; reverted stupidity on 1049/1047 confusion

* Updated CSI API Docs

* merge

* Rearranged Event.cs to Keyboard.cs and Mouse.cs

* Renamed KeyEventEventArgs KeyEventArgs

* temp renamed KeyEvent OldKeyEvent

* Merged KeyEvent into KeyEventArgs

* Renamed Application.ProcessKey members

* Renamed Application.ProcessKey members

* Renamed Application.ProcessKey members

* Added Responder.KeyPressed

* Removed unused references

* Fixed arg naming

* InvokeKeybindings->InvokeKeyBindings

* InvokeKeybindings->InvokeKeyBindings

* Fixed unit tests fail

* More progress on refactoring key input; still broken and probably wrong

* Moved OnKeyPressed out of Responder and made ProcessKeyPrssed non-virtual

* Updated API docs

* Moved key handling from Responder to View

* Updated API docs

* Updated HotKey API docs

* Updated shortcut API docs

* Fixed responder unit tests

* Removed Shortcut from View as it is not used

* Removed unneeded OnHotKey override from Button

* Fixed BackTab logic

* Button now uses Key Bindings exclusively

* Button now uses Key Bindings exclusively

* Updated keyboard.md docs

* Fixed unit tests to account for Toplevel handling default button

* Added View.InvokeCommand API

* Modernized RadioGroup

* Removed ColdKey

* Modernized (partially) StatusBar

* Worked around FileDialog issue with Ctrl-F

* Fixed driver unit test; view must be focused to reciev key pressed

* Application code cleanup

* Start on updaing menu

* Menu now mostly works

* Menu Select refinement

* Fixed known menu bugs!

* Enabled HotKey to cause focus- experimental

* Fixes #3022 & adds unit test to prove it

* Actually Fixes #3022 & adds unit test to prove it

* Working through hotkey issues

* Misc fixes

* removed hot/cold key stuff from Keys scenario

* Fixed scenarios

* Simplified shortcut string handling

* Modernized Checkbox

* Modernized TileView

* Updated API docs

* Updated API docs

* attempting to publish v2 docs

* Revert "attempting to publish v2 docs"

This reverts commit 59dcec111b63121ca34f890d76728f40e81412b3.

* Playing with api docs

* Removed Key.BackTab

* Removed Caps/Scroll/Numlock

* Partial removal of keymodifiers - unit tests pass

* Partial removal of keymodifiers - broke netdriver somewhere

* WindowsDriver & added KeyEventArgsTests

* Fixing menu shortcut/hotkeys - broke Menu.cs into separate files

* Fixed MenuBar!

* Finished modernizing Menu/MenuBar

* Removed Key.a-z. Broke lots of stuff

* checkout@v4

* progress on key mapping and formatting

* VK tests are still failing

* Fixed some unit tests

* Added Hotkey and Keybinding unit tests

* fixed unit test

* All unit tests pass again...

* Fixed broken unit tests

* KeyEventArgs.KeyValue -> AsRune

* Fixed bugs. Still some broken

* Added KeyEventArgs.IsAlpha. Added KeyEventArgs.cast ops. Fixed bugs. Unit tests pass

* Fixed WindowsDriver

* Oops.

* Refactoring based on bdisp's help. Not complete!

* removed calling into subviews from OnKeyBindings

* removed calling into subviews from OnKeyBindings

* Improved View KeyEvent unit tests

* More hotkey unit tests

* BIg change - Got rid of KeyPress w/in Application/Drivers

* Unit tests now pass again

* Refreshed API docs

* Better HotKey logic. More progress. Getting close.

* Fixed handling of shifted chars like ö

* Minor code cleanup

* Minor code cleanup2

* Why is build Action failing?

* Why is build Action failing??

* upgraded to .net8 to try to fix weird CI/CD build errors

* upgraded to .net8 to try to fix weird CI/CD build errors2

* Disabling TextViewTests to diagnose build errors

* reenable TextViewTests to diagnose build errors

* Arrrrrrg

* Merged v2_develop

* Fixed uppercase accented keys in WindowsDriver

* Fixed key binding api docs

* Experimental impl of CommandScope.SubViews for MenuBar

* Removed dead code from application.cs

* Removed dead code from application.cs

* Removed dead code from ConsoleDriver.cs

* Cleaned up some key binding stuff

* Disabled Alt to activate menu for now

* Updated label commands

* Fixed menu bugs. Upgraded menu unit tests

* Fixed unit tests

* Working on NetDriver

* fixed netdriver

* Fixed issues called out by @bdisp CR

* fixed CursesDriver

* added todo to netdriver

* Cherry picked treeview test fix 1b415e5

* Fix NetDriver.

* CommandScope->KeyBindingScope

* Address some tznind feedback

* Refactored KeyBindings big time!

* Added key consts to KeyEventArgs and renamed Key to ConsoleDriverKey

* Fixed some API docs

* Moved ConsoleDriverKey to ConsoleDriver.cs

* Renamed Key->ConsoleDriverKey

* Renamed Key->ConsoleDriverKey

* Renamed Key->ConsoleDriverKey

* renamed file I forgot to rename before

* Updated name and API docs of KeyEventArgs.isAlpha

* Fixed issues with OnKeyUp not doing the right thing.

* Fixed MainLoop.Running never being used

* Fixed MainLoop.Running never being used - unit tests

* Claned up BUGBUG comments

* Disabled a unit test to see why ci/cd tests are failing

* Removed defunct commented code

* Removed more defunct commented code

* Re-eanbled unit test; jsut removing one test case...

* Disabled more...

* Renambed Global->Applicaton and updated scope API docs

* Disabled more unit tests...

* Removed dead code

* Disabled more unit tests...2

* Disabled more unit tests...3

* Renambed Global->Applicaton and updated scope API docs 2

* Added more KeyBinding scope tests

* Added more KeyBinding scope tests2

* ConsoleDriverKey too long. Key too ambiguous. Settled on KeyCode. (Partialy because eventually I want to intro a class named Key).

* KeyEventArgs improvements. cast to Rune must be explicit as it's lossy

* Fixed warnings

* Renamed KeyEventArgs to Key... progress on fixing broken stuff that resulted

* Fix ConsoleKeyMapping bugs.

* Fix NetDriver issue from converting a lower case to a upper case.

* Started migration to Key from KeyCode - e.g. made HotKeys all consistent.

* Fixed build warnings

* Added key defns to Key

* KeyBindings now uses Key vs. KeyCode

* Verified by tweaking UICatalog

* Fixed treeview test ... again

* Renamed ProcessKeyDown/Up to NewKeyDown/Up and OnKeyPressed to OnProcessKeyDown to make things more clear

* Added test AllViews_KeyDown_All_EventsFire unit tests and fixed a few Views that were wrong

* fixed stupid KeyUp event bug

* If key not handled, return false for datefield

* dotnet test --no-restore --verbosity diag

* dotnet test --blame

* run tests on windows

* Fix TestVKPacket unit test and move it to ConsoleKeyMappingTests.cs file.

* Remove unnecessary commented code.

* Tweaked unit tests and removed Key.BareKey

* Fixed little details and updated api docs

* updated api docs

* AddKeyBindingsForHotKey: KeyCode->Key

* Cleaned up more old KeyCode usages. Added TODOs

---------

Co-authored-by: BDisp <[email protected]>
Tig 1 سال پیش
والد
کامیت
dcb3b359ad
100فایلهای تغییر یافته به همراه11943 افزوده شده و 10753 حذف شده
  1. 148 2
      .editorconfig
  2. 1 1
      .github/workflows/api-docs.yml
  3. 4 4
      .github/workflows/dotnet-core.yml
  4. 2 2
      .github/workflows/publish.yml
  5. 1 1
      CONTRIBUTING.md
  6. 1 1
      Example/Example.csproj
  7. 1 1
      ReactiveExample/LoginViewModel.cs
  8. 1 1
      ReactiveExample/README.md
  9. 3 3
      ReactiveExample/ReactiveExample.csproj
  10. 172 174
      Terminal.Gui/Application.cs
  11. 0 2
      Terminal.Gui/Clipboard/Clipboard.cs
  12. 126 0
      Terminal.Gui/Configuration/KeyCodeJsonConverter.cs
  13. 31 110
      Terminal.Gui/Configuration/KeyJsonConverter.cs
  14. 448 32
      Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
  15. 601 0
      Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs
  16. 132 183
      Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
  17. 1 1
      Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs
  18. 8 29
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs
  19. 46 133
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
  20. 2 2
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs
  21. 71 74
      Terminal.Gui/ConsoleDrivers/NetDriver.cs
  22. 230 278
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  23. 1 0
      Terminal.Gui/Drawing/Thickness.cs
  24. 418 391
      Terminal.Gui/Input/Command.cs
  25. 0 560
      Terminal.Gui/Input/ConsoleKeyMapping.cs
  26. 0 837
      Terminal.Gui/Input/Event.cs
  27. 976 0
      Terminal.Gui/Input/Key.cs
  28. 286 0
      Terminal.Gui/Input/KeyBinding.cs
  29. 24 25
      Terminal.Gui/Input/KeyChangedEventArgs.cs
  30. 0 24
      Terminal.Gui/Input/KeyEventEventArgs.cs
  31. 185 0
      Terminal.Gui/Input/Mouse.cs
  32. 151 255
      Terminal.Gui/Input/Responder.cs
  33. 119 237
      Terminal.Gui/Input/ShortcutHelper.cs
  34. 2 2
      Terminal.Gui/MainLoop.cs
  35. 3 12
      Terminal.Gui/Resources/config.json
  36. 3 4
      Terminal.Gui/Terminal.Gui.csproj
  37. 6 6
      Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs
  38. 7 4
      Terminal.Gui/Text/Autocomplete/AutocompleteBase.cs
  39. 8 5
      Terminal.Gui/Text/Autocomplete/IAutocomplete.cs
  40. 11 11
      Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs
  41. 4 4
      Terminal.Gui/Text/CollectionNavigatorBase.cs
  42. 19 15
      Terminal.Gui/Text/TextFormatter.cs
  43. 2 2
      Terminal.Gui/Text/ViewLayout.cs
  44. 20 15
      Terminal.Gui/View/View.cs
  45. 3 0
      Terminal.Gui/View/ViewDrawing.cs
  46. 1 1
      Terminal.Gui/View/ViewEventArgs.cs
  47. 610 389
      Terminal.Gui/View/ViewKeyboard.cs
  48. 0 5
      Terminal.Gui/View/ViewText.cs
  49. 195 270
      Terminal.Gui/Views/Button.cs
  50. 205 197
      Terminal.Gui/Views/CheckBox.cs
  51. 4 14
      Terminal.Gui/Views/ColorPicker.cs
  52. 10 20
      Terminal.Gui/Views/ComboBox.cs
  53. 0 234
      Terminal.Gui/Views/ContextMenu.cs
  54. 353 348
      Terminal.Gui/Views/DateField.cs
  55. 5 4
      Terminal.Gui/Views/Dialog.cs
  56. 61 66
      Terminal.Gui/Views/FileDialog.cs
  57. 1 1
      Terminal.Gui/Views/GraphView/Annotations.cs
  58. 6 18
      Terminal.Gui/Views/GraphView/GraphView.cs
  59. 559 531
      Terminal.Gui/Views/HexView.cs
  60. 23 17
      Terminal.Gui/Views/Label.cs
  61. 15 25
      Terminal.Gui/Views/ListView.cs
  62. 0 2215
      Terminal.Gui/Views/Menu.cs
  63. 242 0
      Terminal.Gui/Views/Menu/ContextMenu.cs
  64. 1029 0
      Terminal.Gui/Views/Menu/Menu.cs
  65. 1467 0
      Terminal.Gui/Views/Menu/MenuBar.cs
  66. 97 0
      Terminal.Gui/Views/Menu/MenuEventArgs.cs
  67. 0 98
      Terminal.Gui/Views/MenuEventArgs.cs
  68. 339 344
      Terminal.Gui/Views/RadioGroup.cs
  69. 17 17
      Terminal.Gui/Views/ScrollView.cs
  70. 21 35
      Terminal.Gui/Views/Slider.cs
  71. 245 222
      Terminal.Gui/Views/StatusBar.cs
  72. 4 16
      Terminal.Gui/Views/TabView.cs
  73. 1 1
      Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapper.cs
  74. 60 61
      Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapperByObject.cs
  75. 1 1
      Terminal.Gui/Views/TableView/ColumnStyle.cs
  76. 1 1
      Terminal.Gui/Views/TableView/TableStyle.cs
  77. 50 46
      Terminal.Gui/Views/TableView/TableView.cs
  78. 5 5
      Terminal.Gui/Views/TableView/TreeTableSource.cs
  79. 200 196
      Terminal.Gui/Views/TextField.cs
  80. 13 16
      Terminal.Gui/Views/TextValidateField.cs
  81. 109 103
      Terminal.Gui/Views/TextView.cs
  82. 76 87
      Terminal.Gui/Views/TileView.cs
  83. 26 26
      Terminal.Gui/Views/TimeField.cs
  84. 42 103
      Terminal.Gui/Views/Toplevel.cs
  85. 2 0
      Terminal.Gui/Views/TreeView/Branch.cs
  86. 32 34
      Terminal.Gui/Views/TreeView/TreeView.cs
  87. 457 465
      Terminal.Gui/Views/Wizard/Wizard.cs
  88. 9 9
      UICatalog/KeyBindingsDialog.cs
  89. 4 0
      UICatalog/Properties/launchSettings.json
  90. 10 10
      UICatalog/Scenarios/ASCIICustomButton.cs
  91. 2 2
      UICatalog/Scenarios/AllViewsTester.cs
  92. 7 7
      UICatalog/Scenarios/BackgroundWorkerCollection.cs
  93. 242 244
      UICatalog/Scenarios/Buttons.cs
  94. 113 112
      UICatalog/Scenarios/CharacterMap.cs
  95. 1 1
      UICatalog/Scenarios/CollectionNavigatorTester.cs
  96. 3 3
      UICatalog/Scenarios/ConfigurationEditor.cs
  97. 132 126
      UICatalog/Scenarios/ContextMenus.cs
  98. 5 5
      UICatalog/Scenarios/CsvEditor.cs
  99. 13 16
      UICatalog/Scenarios/DynamicMenuBar.cs
  100. 540 543
      UICatalog/Scenarios/DynamicStatusBar.cs

+ 148 - 2
.editorconfig

@@ -16,8 +16,154 @@ csharp_space_between_method_declaration_parameter_list_parentheses = false
 csharp_space_between_method_call_parameter_list_parentheses = false
 csharp_preserve_single_line_blocks = true
 dotnet_style_require_accessibility_modifiers = never
-csharp_style_var_when_type_is_apparent = true
-csharp_prefer_braces = false
+csharp_style_var_when_type_is_apparent = true:none
+csharp_prefer_braces = true:none
 csharp_space_before_open_square_brackets = true
 csharp_space_between_method_call_name_and_opening_parenthesis = true
 csharp_space_between_method_declaration_name_and_open_parenthesis = true
+
+# Microsoft .NET properties
+csharp_style_var_elsewhere = true:none
+
+# ReSharper properties
+resharper_align_linq_query = true
+resharper_align_multiline_calls_chain = true
+resharper_align_multiline_extends_list = true
+resharper_align_multiline_parameter = true
+resharper_blank_lines_around_region = 1
+resharper_braces_redundant = true
+resharper_csharp_stick_comment = false
+resharper_force_attribute_style = separate
+resharper_indent_type_constraints = true
+resharper_local_function_body = expression_body
+resharper_remove_blank_lines_near_braces_in_declarations = true
+resharper_use_roslyn_logic_for_evident_types = true
+csharp_space_around_binary_operators = before_and_after
+csharp_using_directive_placement = outside_namespace:silent
+csharp_prefer_simple_using_statement = true:suggestion
+csharp_style_namespace_declarations = file_scoped:none
+csharp_style_prefer_method_group_conversion = true:silent
+csharp_style_prefer_top_level_statements = true:silent
+csharp_style_prefer_primary_constructors = true:suggestion
+csharp_style_expression_bodied_methods = true:none
+csharp_style_expression_bodied_constructors = true:none
+csharp_style_expression_bodied_operators = false:silent
+csharp_style_expression_bodied_properties = true:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_accessors = true:silent
+csharp_style_expression_bodied_lambdas = true:silent
+csharp_style_expression_bodied_local_functions = false:silent
+csharp_style_throw_expression = true:suggestion
+csharp_style_prefer_null_check_over_type_check = true:suggestion
+csharp_prefer_simple_default_expression = true:suggestion
+csharp_style_prefer_local_over_anonymous_function = true:suggestion
+csharp_style_prefer_index_operator = true:suggestion
+csharp_style_prefer_range_operator = true:suggestion
+csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
+csharp_style_prefer_tuple_swap = true:suggestion
+csharp_style_prefer_utf8_string_literals = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+csharp_style_deconstructed_variable_declaration = true:suggestion
+csharp_style_unused_value_assignment_preference = discard_variable:suggestion
+csharp_style_unused_value_expression_statement_preference = discard_variable:silent
+csharp_indent_case_contents_when_block = true
+csharp_prefer_static_local_function = true:suggestion
+csharp_style_prefer_readonly_struct = true:suggestion
+csharp_style_prefer_readonly_struct_member = true:suggestion
+csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
+csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent
+csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
+csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent
+csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent
+csharp_style_conditional_delegate_call = true:suggestion
+csharp_style_prefer_switch_expression = true:suggestion
+csharp_style_prefer_pattern_matching = true:silent
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+csharp_style_prefer_not_pattern = true:suggestion
+csharp_style_prefer_extended_property_pattern = true:suggestion
+csharp_style_var_for_built_in_types = false:silent
+
+[*.{cs,vb}]
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+tab_width = 8
+indent_size = 8
+end_of_line = crlf
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_auto_properties = true:silent
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_prefer_collection_expression = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:silent
+dotnet_style_prefer_conditional_expression_over_return = true:silent
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_compound_assignment = true:suggestion
+dotnet_style_prefer_simplified_interpolation = true:suggestion
+dotnet_style_namespace_match_folder = true:suggestion
+[*.{cs,vb}]
+#### Naming styles ####
+
+# Naming rules
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# Symbol specifications
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers = 
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.required_modifiers = 
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers = 
+
+# Naming styles
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix = 
+dotnet_naming_style.begins_with_i.word_separator = 
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix = 
+dotnet_naming_style.pascal_case.required_suffix = 
+dotnet_naming_style.pascal_case.word_separator = 
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix = 
+dotnet_naming_style.pascal_case.required_suffix = 
+dotnet_naming_style.pascal_case.word_separator = 
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+dotnet_style_readonly_field = true:suggestion
+dotnet_style_predefined_type_for_locals_parameters_members = true:silent
+dotnet_style_predefined_type_for_member_access = true:silent
+dotnet_style_require_accessibility_modifiers = never:silent
+dotnet_style_allow_multiple_blank_lines_experimental = true:silent
+dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
+dotnet_code_quality_unused_parameters = all:suggestion
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
+dotnet_style_qualification_for_field = false:silent
+dotnet_style_qualification_for_property = false:silent
+dotnet_style_qualification_for_method = false:silent
+dotnet_style_qualification_for_event = false:silent

+ 1 - 1
.github/workflows/api-docs.yml

@@ -19,7 +19,7 @@ jobs:
     runs-on: windows-latest
     steps:
     - name: Checkout
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
 
     - name: DocFX Build
       working-directory: docfx

+ 4 - 4
.github/workflows/dotnet-core.yml

@@ -9,15 +9,15 @@ on:
 jobs:
   build_and_test:
 
-    runs-on: ubuntu-latest
+    runs-on: windows-latest
 
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     
     - name: Setup dotnet
       uses: actions/setup-dotnet@v3
       with:
-        dotnet-version: 7.0
+        dotnet-version: 8.0
         dotnet-quality: 'ga'
 
     - name: Install dependencies
@@ -30,7 +30,7 @@ jobs:
     - name: Test
       run: |
         sed -i 's/"stopOnFail": false/"stopOnFail": true/g' UnitTests/xunit.runner.json
-        dotnet test --no-restore --verbosity normal --collect:"XPlat Code Coverage"  --settings UnitTests/coverlet.runsettings
+        dotnet test --no-restore --verbosity normal --collect:"XPlat Code Coverage"  --settings UnitTests/coverlet.runsettings --blame
         mv -v UnitTests/TestResults/*/*.* UnitTests/TestResults/
 
     # Note: this step is currently not writing to the gist for some reason

+ 2 - 2
.github/workflows/publish.yml

@@ -14,7 +14,7 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
       with:
         fetch-depth: 0 # fetch-depth is needed for GitVersion
 
@@ -34,7 +34,7 @@ jobs:
     - name: Setup dotnet
       uses: actions/setup-dotnet@v3
       with:
-        dotnet-version: 7.0
+        dotnet-version: 8.0
         dotnet-quality: 'ga'
         
     - name: Install dependencies

+ 1 - 1
CONTRIBUTING.md

@@ -156,7 +156,7 @@ The [Microsoft .NET Framework Design Guidelines](https://docs.microsoft.com/en-u
    - Sub-classes of the class implementing `EventToRaise` can override `OnEventToRaise` as needed.
 4. Where possible, a subclass of `EventArgs` should be provided and the old and new state should be included. By doing this, event handler methods do not have to query the sender for state.
 
-See also: https://www.codeproject.com/docs/20550/C-Event-Implementation-Fundamentals-Best-Practices
+See also: https://www.codeproject.com../docs/20550/C-Event-Implementation-Fundamentals-Best-Practices
 
 ### Defining new `View` classes
 

+ 1 - 1
Example/Example.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <OutputType>Exe</OutputType>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <!-- Version numbers are automatically updated by gitversion when a release is released -->
     <!-- In the source tree the version will always be 1.0 for all projects. -->
     <!-- Do not modify these. -->

+ 1 - 1
ReactiveExample/LoginViewModel.cs

@@ -17,7 +17,7 @@ namespace ReactiveExample {
 	// We mark the view model with the [DataContract] attributes and this
 	// allows you to save the view model class to the disk, and then to read
 	// the view model from the disk, making your app state persistent.
-	// See also: https://www.reactiveui.net/docs/handbook/data-persistence/
+	// See also: https://www.reactiveui.net../docs/handbook/data-persistence/
 	//
 	[DataContract]
 	public class LoginViewModel : ReactiveObject {

+ 1 - 1
ReactiveExample/README.md

@@ -17,7 +17,7 @@ From now on, you can use `.ObserveOn(RxApp.MainThreadScheduler)` to return to th
 
 ### Data Bindings
 
-If you wish to implement `OneWay` data binding, then use the `WhenAnyValue` [ReactiveUI extension method](https://www.reactiveui.net/docs/handbook/when-any/) that listens to `INotifyPropertyChanged` events of the specified property, and converts that events into `IObservable<TProperty>`:
+If you wish to implement `OneWay` data binding, then use the `WhenAnyValue` [ReactiveUI extension method](https://www.reactiveui.net../docs/handbook/when-any/) that listens to `INotifyPropertyChanged` events of the specified property, and converts that events into `IObservable<TProperty>`:
 
 ```cs
 // 'usernameInput' is 'TextField' 

+ 3 - 3
ReactiveExample/ReactiveExample.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <OutputType>Exe</OutputType>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <!-- Version numbers are automatically updated by gitversion when a release is released -->
     <!-- In the source tree the version will always be 2.0 for all projects. -->
     <!-- Do not modify these. -->
@@ -11,8 +11,8 @@
     <InformationalVersion>2.0</InformationalVersion>
   </PropertyGroup>
   <ItemGroup>
-    <PackageReference Include="ReactiveUI.Fody" Version="19.5.1" />
-    <PackageReference Include="ReactiveUI" Version="19.5.1" />
+    <PackageReference Include="ReactiveUI.Fody" Version="19.5.31" />
+    <PackageReference Include="ReactiveUI" Version="19.5.31" />
     <PackageReference Include="ReactiveMarbles.ObservableEvents.SourceGenerator" Version="1.3.1" PrivateAssets="all" />
   </ItemGroup>
   <ItemGroup>

+ 172 - 174
Terminal.Gui/Application.cs

@@ -8,6 +8,7 @@ using System.IO;
 using System.Text.Json.Serialization;
 
 namespace Terminal.Gui;
+
 /// <summary>
 /// A static, singleton class representing the application. This class is the entry point for the application.
 /// </summary>
@@ -28,20 +29,9 @@ namespace Terminal.Gui;
 /// </code>
 /// </example>
 /// <remarks>
-///  <para>
-///   Creates a instance of <see cref="Terminal.Gui.MainLoop"/> to process input events, handle timers and
-///   other sources of data. It is accessible via the <see cref="MainLoop"/> property.
-///  </para>
-///  <para>
-///   The <see cref="Iteration"/> event is invoked on each iteration of the <see cref="Terminal.Gui.MainLoop"/>.
-///  </para>
-///  <para>
-///   When invoked it sets the <see cref="SynchronizationContext"/> to one that is tied
-///   to the <see cref="MainLoop"/>, allowing user code to use async/await.
-///  </para>
+/// TODO: Flush this out.
 /// </remarks>
 public static partial class Application {
-
 	/// <summary>
 	/// Gets the <see cref="ConsoleDriver"/> that has been selected. See also <see cref="UseSystemConsole"/>.
 	/// </summary>
@@ -64,19 +54,19 @@ public static partial class Application {
 	// For Unit testing - ignores UseSystemConsole
 	internal static bool _forceFakeConsole;
 
-	private static List<CultureInfo> _cachedSupportedCultures;
+	static List<CultureInfo> _cachedSupportedCultures;
 
 	/// <summary>
 	/// Gets all cultures supported by the application without the invariant language.
 	/// </summary>
 	public static List<CultureInfo> SupportedCultures => _cachedSupportedCultures;
 
-	private static List<CultureInfo> GetSupportedCultures ()
+	static List<CultureInfo> GetSupportedCultures ()
 	{
-		CultureInfo [] culture = CultureInfo.GetCultures (CultureTypes.AllCultures);
+		var culture = CultureInfo.GetCultures (CultureTypes.AllCultures);
 
 		// Get the assembly
-		Assembly assembly = Assembly.GetExecutingAssembly ();
+		var assembly = Assembly.GetExecutingAssembly ();
 
 		//Find the location of the assembly
 		string assemblyLocation = AppDomain.CurrentDomain.BaseDirectory;
@@ -86,13 +76,12 @@ public static partial class Application {
 
 		// Return all culture for which satellite folder found with culture code.
 		return culture.Where (cultureInfo =>
-		   Directory.Exists (Path.Combine (assemblyLocation, cultureInfo.Name)) &&
-		   File.Exists (Path.Combine (assemblyLocation, cultureInfo.Name, resourceFilename))
+			Directory.Exists (Path.Combine (assemblyLocation, cultureInfo.Name)) &&
+			File.Exists (Path.Combine (assemblyLocation, cultureInfo.Name, resourceFilename))
 		).ToList ();
 	}
 
 	#region Initialization (Init/Shutdown)
-
 	/// <summary>
 	/// Initializes a new instance of <see cref="Terminal.Gui"/> Application. 
 	/// </summary>
@@ -155,17 +144,19 @@ public static partial class Application {
 		// multiple times. We need to do this because some settings are only
 		// valid after a Driver is loaded. In this cases we need just 
 		// `Settings` so we can determine which driver to use.
-		ConfigurationManager.Load (true);
-		ConfigurationManager.Apply ();
+		Load (true);
+		Apply ();
 
 		Driver ??= Environment.OSVersion.Platform switch {
 			_ when _forceFakeConsole => new FakeDriver (), // for unit testing only
 			_ when UseSystemConsole => new NetDriver (),
 			PlatformID.Win32NT or PlatformID.Win32S or PlatformID.Win32Windows => new WindowsDriver (),
-			_ => new CursesDriver (),
+			_ => new CursesDriver ()
 		};
 
-		if (Driver == null) throw new InvalidOperationException ("Init could not determine the ConsoleDriver to use.");
+		if (Driver == null) {
+			throw new InvalidOperationException ("Init could not determine the ConsoleDriver to use.");
+		}
 
 		try {
 			MainLoop = Driver.Init ();
@@ -177,11 +168,10 @@ public static partial class Application {
 			throw new InvalidOperationException ("Unable to initialize the console. This can happen if the console is already in use by another process or in unit tests.", ex);
 		}
 
-		Driver.SizeChanged += (s, args) => OnSizeChanging (args);
-		Driver.KeyPressed += (s, args) => OnKeyPressed (args);
-		Driver.KeyDown += (s, args) => OnKeyDown (args);
-		Driver.KeyUp += (s, args) => OnKeyUp (args);
-		Driver.MouseEvent += (s, args) => OnMouseEvent (args);
+		Driver.SizeChanged += Driver_SizeChanged;
+		Driver.KeyDown += Driver_KeyDown;
+		Driver.KeyUp += Driver_KeyUp;
+		Driver.MouseEvent += Driver_MouseEvent;
 
 		SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext ());
 
@@ -192,6 +182,14 @@ public static partial class Application {
 		_initialized = true;
 	}
 
+	static void Driver_SizeChanged (object sender, SizeChangedEventArgs e) => OnSizeChanging (e);
+
+	static void Driver_KeyDown (object sender, Key e) => OnKeyDown (e);
+
+	static void Driver_KeyUp (object sender, Key e) => OnKeyUp (e);
+
+	static void Driver_MouseEvent (object sender, MouseEventEventArgs e) => OnMouseEvent (e);
+
 
 	/// <summary>
 	/// Shutdown an application initialized with <see cref="Init(ConsoleDriver)"/>.
@@ -203,7 +201,7 @@ public static partial class Application {
 	public static void Shutdown ()
 	{
 		ResetState ();
-		ConfigurationManager.PrintJsonErrors ();
+		PrintJsonErrors ();
 	}
 
 	// Encapsulate all setting of initial state for Application; Having
@@ -228,13 +226,18 @@ public static partial class Application {
 
 		MainLoop?.Dispose ();
 		MainLoop = null;
-		Driver?.End ();
-		Driver = null;
+		if (Driver != null) {
+			Driver.SizeChanged -= Driver_SizeChanged;
+			Driver.KeyDown -= Driver_KeyDown;
+			Driver.KeyUp -= Driver_KeyUp;
+			Driver.MouseEvent -= Driver_MouseEvent;
+			Driver?.End ();
+			Driver = null;
+		}
 		Iteration = null;
 		MouseEvent = null;
 		KeyDown = null;
 		KeyUp = null;
-		KeyPressed = null;
 		SizeChanging = null;
 		_mainThreadId = -1;
 		NotifyNewRunState = null;
@@ -249,11 +252,9 @@ public static partial class Application {
 		// (https://github.com/gui-cs/Terminal.Gui/issues/1084).
 		SynchronizationContext.SetSynchronizationContext (syncContext: null);
 	}
-
 	#endregion Initialization (Init/Shutdown)
 
 	#region Run (Begin, Run, End, Stop)
-
 	/// <summary>
 	/// Notify that a new <see cref="RunState"/> was created (<see cref="Begin(Toplevel)"/> was called). The token is created in 
 	/// <see cref="Begin(Toplevel)"/> and this event will be fired before that function exits.
@@ -317,8 +318,8 @@ public static partial class Application {
 				Top.OnLeave (Toplevel);
 			}
 			if (string.IsNullOrEmpty (Toplevel.Id)) {
-				var count = 1;
-				var id = (_topLevels.Count + count).ToString ();
+				int count = 1;
+				string id = (_topLevels.Count + count).ToString ();
 				while (_topLevels.Count > 0 && _topLevels.FirstOrDefault (x => x.Id == id) != null) {
 					count++;
 					id = (_topLevels.Count + count).ToString ();
@@ -341,9 +342,9 @@ public static partial class Application {
 			Top = Toplevel;
 		}
 
-		var refreshDriver = true;
-		if (OverlappedTop == null || Toplevel.IsOverlappedContainer || (Current?.Modal == false && Toplevel.Modal)
-			|| (Current?.Modal == false && !Toplevel.Modal) || (Current?.Modal == true && Toplevel.Modal)) {
+		bool refreshDriver = true;
+		if (OverlappedTop == null || Toplevel.IsOverlappedContainer || Current?.Modal == false && Toplevel.Modal
+		|| Current?.Modal == false && !Toplevel.Modal || Current?.Modal == true && Toplevel.Modal) {
 
 			if (Toplevel.Visible) {
 				Current = Toplevel;
@@ -351,8 +352,8 @@ public static partial class Application {
 			} else {
 				refreshDriver = false;
 			}
-		} else if ((OverlappedTop != null && Toplevel != OverlappedTop && Current?.Modal == true && !_topLevels.Peek ().Modal)
-			|| (OverlappedTop != null && Toplevel != OverlappedTop && Current?.Running == false)) {
+		} else if (OverlappedTop != null && Toplevel != OverlappedTop && Current?.Modal == true && !_topLevels.Peek ().Modal
+			|| OverlappedTop != null && Toplevel != OverlappedTop && Current?.Running == false) {
 			refreshDriver = false;
 			MoveCurrent (Toplevel);
 		} else {
@@ -406,7 +407,7 @@ public static partial class Application {
 	/// platform will be used (<see cref="WindowsDriver"/>, <see cref="CursesDriver"/>, or <see cref="NetDriver"/>).
 	/// Must be <see langword="null"/> if <see cref="Init(ConsoleDriver)"/> has already been called. 
 	/// </param>
-	public static void Run<T> (Func<Exception, bool> errorHandler = null, ConsoleDriver driver = null) where T : Toplevel, new()
+	public static void Run<T> (Func<Exception, bool> errorHandler = null, ConsoleDriver driver = null) where T : Toplevel, new ()
 	{
 		if (_initialized) {
 			if (Driver != null) {
@@ -426,7 +427,7 @@ public static partial class Application {
 			}
 		} else {
 			// Init() has NOT been called.
-			InternalInit (() => new T (), driver, calledViaRunT: true);
+			InternalInit (() => new T (), driver, true);
 			Run (Top, errorHandler);
 		}
 	}
@@ -465,7 +466,7 @@ public static partial class Application {
 	/// <param name="errorHandler">RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true, rethrows when null).</param>
 	public static void Run (Toplevel view, Func<Exception, bool> errorHandler = null)
 	{
-		var resume = true;
+		bool resume = true;
 		while (resume) {
 #if !DEBUG
 				try {
@@ -520,13 +521,10 @@ public static partial class Application {
 	///   Runs <paramref name="action"/> on the thread that is processing events
 	/// </summary>
 	/// <param name="action">the action to be invoked on the main processing thread.</param>
-	public static void Invoke (Action action)
-	{
-		MainLoop?.AddIdle (() => {
-			action ();
-			return false;
-		});
-	}
+	public static void Invoke (Action action) => MainLoop?.AddIdle (() => {
+		action ();
+		return false;
+	});
 
 	// TODO: Determine if this is really needed. The only code that calls WakeUp I can find
 	// is ProgressBarStyles and it's not clear it needs to.
@@ -556,7 +554,7 @@ public static partial class Application {
 	}
 
 	/// <summary>
-	///  This event is raised on each iteration of the <see cref="MainLoop"/>. 
+	///  This event is raised on each iteration of the main loop.
 	/// </summary>
 	/// <remarks>
 	///  See also <see cref="Timeout"/>
@@ -580,26 +578,20 @@ public static partial class Application {
 	// users use async/await on their code
 	//
 	class MainLoopSyncContext : SynchronizationContext {
-		public override SynchronizationContext CreateCopy ()
-		{
-			return new MainLoopSyncContext ();
-		}
+		public override SynchronizationContext CreateCopy () => new MainLoopSyncContext ();
 
-		public override void Post (SendOrPostCallback d, object state)
-		{
-			MainLoop.AddIdle (() => {
-				d (state);
-				return false;
-			});
-			//_mainLoop.Driver.Wakeup ();
-		}
+		public override void Post (SendOrPostCallback d, object state) => MainLoop.AddIdle (() => {
+			d (state);
+			return false;
+		});
 
+		//_mainLoop.Driver.Wakeup ();
 		public override void Send (SendOrPostCallback d, object state)
 		{
 			if (Thread.CurrentThread.ManagedThreadId == _mainThreadId) {
 				d (state);
 			} else {
-				var wasExecuted = false;
+				bool wasExecuted = false;
 				Invoke (() => {
 					d (state);
 					wasExecuted = true;
@@ -612,7 +604,7 @@ public static partial class Application {
 	}
 
 	/// <summary>
-	///  Building block API: Runs the <see cref="MainLoop"/> for the created <see cref="Toplevel"/>.
+	///  Building block API: Runs the main loop for the created <see cref="Toplevel"/>.
 	/// </summary>
 	/// <param name="state">The state returned by the <see cref="Begin(Toplevel)"/> method.</param>
 	public static void RunLoop (RunState state)
@@ -624,13 +616,18 @@ public static partial class Application {
 			throw new ObjectDisposedException ("state");
 		}
 
-		var firstIteration = true;
+		bool firstIteration = true;
 		for (state.Toplevel.Running = true; state.Toplevel.Running;) {
+			MainLoop.Running = true;
 			if (EndAfterFirstIteration && !firstIteration) {
 				return;
 			}
 			RunIteration (ref state, ref firstIteration);
 		}
+		MainLoop.Running = false;
+		// Run one last iteration to consume any outstanding input events from Driver
+		// This is important for remaining OnKeyUp events.
+		RunIteration (ref state, ref firstIteration);
 	}
 
 	/// <summary>
@@ -641,7 +638,7 @@ public static partial class Application {
 	/// it will be set to <see langword="false"/> if at least one iteration happened.</param>
 	public static void RunIteration (ref RunState state, ref bool firstIteration)
 	{
-		if (MainLoop.EventsPending ()) {
+		if (MainLoop.EventsPending () && MainLoop.Running) {
 			// Notify Toplevel it's ready
 			if (firstIteration) {
 				state.Toplevel.OnReady ();
@@ -649,7 +646,6 @@ public static partial class Application {
 
 			MainLoop.RunIteration ();
 			Iteration?.Invoke (null, new IterationEventArgs ());
-
 			EnsureModalOrVisibleAlwaysOnTop (state.Toplevel);
 			if (state.Toplevel != Current) {
 				OverlappedTop?.OnDeactivate (state.Toplevel);
@@ -663,7 +659,7 @@ public static partial class Application {
 		firstIteration = false;
 
 		if (state.Toplevel != Top &&
-			(Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded)) {
+		(Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded)) {
 			state.Toplevel.SetNeedsDisplay (state.Toplevel.Frame);
 			Top.Draw ();
 			foreach (var top in _topLevels.Reverse ()) {
@@ -675,16 +671,16 @@ public static partial class Application {
 			}
 		}
 		if (_topLevels.Count == 1 && state.Toplevel == Top
-			&& (Driver.Cols != state.Toplevel.Frame.Width || Driver.Rows != state.Toplevel.Frame.Height)
-			&& (state.Toplevel.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded)) {
+					&& (Driver.Cols != state.Toplevel.Frame.Width || Driver.Rows != state.Toplevel.Frame.Height)
+					&& (state.Toplevel.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded)) {
 
 			state.Toplevel.Clear (new Rect (Point.Empty, new Size (Driver.Cols, Driver.Rows)));
 		}
 
 		if (state.Toplevel.NeedsDisplay ||
-			state.Toplevel.SubViewNeedsDisplay ||
-			state.Toplevel.LayoutNeeded ||
-			OverlappedChildNeedsDisplay ()) {
+		state.Toplevel.SubViewNeedsDisplay ||
+		state.Toplevel.LayoutNeeded ||
+		OverlappedChildNeedsDisplay ()) {
 			state.Toplevel.Draw ();
 			state.Toplevel.PositionCursor ();
 			Driver.Refresh ();
@@ -692,8 +688,8 @@ public static partial class Application {
 			Driver.UpdateCursor ();
 		}
 		if (state.Toplevel != Top &&
-			!state.Toplevel.Modal &&
-			(Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded)) {
+		!state.Toplevel.Modal &&
+		(Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded)) {
 			Top.Draw ();
 		}
 	}
@@ -713,12 +709,12 @@ public static partial class Application {
 	/// </remarks>
 	public static void RequestStop (Toplevel top = null)
 	{
-		if (OverlappedTop == null || top == null || (OverlappedTop == null && top != null)) {
+		if (OverlappedTop == null || top == null || OverlappedTop == null && top != null) {
 			top = Current;
 		}
 
 		if (OverlappedTop != null && top.IsOverlappedContainer && top?.Running == true
-			&& (Current?.Modal == false || (Current?.Modal == true && Current?.Running == false))) {
+		&& (Current?.Modal == false || Current?.Modal == true && Current?.Running == false)) {
 
 			OverlappedTop.RequestStop ();
 		} else if (OverlappedTop != null && top != Current && Current?.Running == true && Current?.Modal == true
@@ -738,10 +734,10 @@ public static partial class Application {
 			OnNotifyStopRunState (Current);
 			top.Running = false;
 			OnNotifyStopRunState (top);
-		} else if ((OverlappedTop != null && top != OverlappedTop && top != Current && Current?.Modal == false
-			&& Current?.Running == true && !top.Running)
-			|| (OverlappedTop != null && top != OverlappedTop && top != Current && Current?.Modal == false
-			&& Current?.Running == false && !top.Running && _topLevels.ToArray () [1].Running)) {
+		} else if (OverlappedTop != null && top != OverlappedTop && top != Current && Current?.Modal == false
+			&& Current?.Running == true && !top.Running
+			|| OverlappedTop != null && top != OverlappedTop && top != Current && Current?.Modal == false
+			&& Current?.Running == false && !top.Running && _topLevels.ToArray () [1].Running) {
 
 			MoveCurrent (top);
 		} else if (OverlappedTop != null && Current != top && Current?.Running == true && !top.Running
@@ -757,7 +753,7 @@ public static partial class Application {
 			OnNotifyStopRunState (Current);
 		} else {
 			Toplevel currentTop;
-			if (top == Current || (Current?.Modal == true && !top.Modal)) {
+			if (top == Current || Current?.Modal == true && !top.Modal) {
 				currentTop = Current;
 			} else {
 				currentTop = top;
@@ -814,7 +810,7 @@ public static partial class Application {
 
 		// If there is a OverlappedTop that is not the RunState.Toplevel then runstate.TopLevel 
 		// is a child of MidTop and we should notify the OverlappedTop that it is closing
-		if (OverlappedTop != null && !(runState.Toplevel).Modal && runState.Toplevel != OverlappedTop) {
+		if (OverlappedTop != null && !runState.Toplevel.Modal && runState.Toplevel != OverlappedTop) {
 			OverlappedTop.OnChildClosed (runState.Toplevel);
 		}
 
@@ -837,11 +833,10 @@ public static partial class Application {
 		runState.Toplevel = null;
 		runState.Dispose ();
 	}
-
 	#endregion Run (Begin, Run, End)
 
 	#region Toplevel handling
-	static readonly Stack<Toplevel> _topLevels = new Stack<Toplevel> ();
+	static readonly Stack<Toplevel> _topLevels = new ();
 
 	/// <summary>
 	/// The <see cref="Toplevel"/> object used for the application on startup (<seealso cref="Application.Top"/>)
@@ -858,7 +853,7 @@ public static partial class Application {
 
 	static void EnsureModalOrVisibleAlwaysOnTop (Toplevel Toplevel)
 	{
-		if (!Toplevel.Running || (Toplevel == Current && Toplevel.Visible) || OverlappedTop == null || _topLevels.Peek ().Modal) {
+		if (!Toplevel.Running || Toplevel == Current && Toplevel.Visible || OverlappedTop == null || _topLevels.Peek ().Modal) {
 			return;
 		}
 
@@ -886,8 +881,8 @@ public static partial class Application {
 		if (_topLevels != null) {
 			int count = _topLevels.Count;
 			if (count > 0) {
-				var rx = x - startFrame.X;
-				var ry = y - startFrame.Y;
+				int rx = x - startFrame.X;
+				int ry = y - startFrame.Y;
 				foreach (var t in _topLevels) {
 					if (t != Current) {
 						if (t != start && t.Visible && t.Frame.Contains (rx, ry)) {
@@ -905,7 +900,7 @@ public static partial class Application {
 
 	static View FindTopFromView (View view)
 	{
-		View top = view?.SuperView != null && view?.SuperView != Top
+		var top = view?.SuperView != null && view?.SuperView != Top
 			? view.SuperView : view;
 
 		while (top?.SuperView != null && top?.SuperView != Top) {
@@ -923,7 +918,7 @@ public static partial class Application {
 			lock (_topLevels) {
 				_topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ());
 			}
-			var index = 0;
+			int index = 0;
 			var savedToplevels = _topLevels.ToArray ();
 			foreach (var t in savedToplevels) {
 				if (!t.Modal && t != Current && t != top && t != savedToplevels [index]) {
@@ -941,7 +936,7 @@ public static partial class Application {
 			lock (_topLevels) {
 				_topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ());
 			}
-			var index = 0;
+			int index = 0;
 			foreach (var t in _topLevels.ToArray ()) {
 				if (!t.Running && t != Current && index > 0) {
 					lock (_topLevels) {
@@ -952,10 +947,10 @@ public static partial class Application {
 			}
 			return false;
 		}
-		if ((OverlappedTop != null && top?.Modal == true && _topLevels.Peek () != top)
-			|| (OverlappedTop != null && Current != OverlappedTop && Current?.Modal == false && top == OverlappedTop)
-			|| (OverlappedTop != null && Current?.Modal == false && top != Current)
-			|| (OverlappedTop != null && Current?.Modal == true && top == OverlappedTop)) {
+		if (OverlappedTop != null && top?.Modal == true && _topLevels.Peek () != top
+		|| OverlappedTop != null && Current != OverlappedTop && Current?.Modal == false && top == OverlappedTop
+		|| OverlappedTop != null && Current?.Modal == false && top != Current
+		|| OverlappedTop != null && Current?.Modal == true && top == OverlappedTop) {
 			lock (_topLevels) {
 				_topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ());
 				Current = top;
@@ -995,7 +990,6 @@ public static partial class Application {
 		Refresh ();
 		return true;
 	}
-
 	#endregion Toplevel handling
 
 	#region Mouse handling
@@ -1176,9 +1170,9 @@ public static partial class Application {
 		}
 
 		if ((view == null || view == OverlappedTop) &&
-			Current is { Modal: false } && OverlappedTop != null &&
-			a.MouseEvent.Flags != MouseFlags.ReportMousePosition &&
-			a.MouseEvent.Flags != 0) {
+		Current is { Modal: false } && OverlappedTop != null &&
+		a.MouseEvent.Flags != MouseFlags.ReportMousePosition &&
+		a.MouseEvent.Flags != 0) {
 
 			var top = FindDeepestTop (Top, a.MouseEvent.X, a.MouseEvent.Y, out _, out _);
 			view = View.FindDeepestView (top, a.MouseEvent.X, a.MouseEvent.Y, out screenX, out screenY);
@@ -1294,13 +1288,12 @@ public static partial class Application {
 	#endregion Mouse handling
 
 	#region Keyboard handling
-
-	static Key _alternateForwardKey = Key.PageDown | Key.CtrlMask;
+	static Key _alternateForwardKey = new (KeyCode.PageDown | KeyCode.CtrlMask);
 
 	/// <summary>
 	/// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.
 	/// </summary>
-	[SerializableConfigurationProperty (Scope = typeof (SettingsScope)), JsonConverter (typeof (KeyJsonConverter))]
+	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))] [JsonConverter (typeof (KeyJsonConverter))]
 	public static Key AlternateForwardKey {
 		get => _alternateForwardKey;
 		set {
@@ -1319,12 +1312,12 @@ public static partial class Application {
 		}
 	}
 
-	static Key _alternateBackwardKey = Key.PageUp | Key.CtrlMask;
+	static Key _alternateBackwardKey = new (KeyCode.PageUp | KeyCode.CtrlMask);
 
 	/// <summary>
 	/// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.
 	/// </summary>
-	[SerializableConfigurationProperty (Scope = typeof (SettingsScope)), JsonConverter (typeof (KeyJsonConverter))]
+	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))] [JsonConverter (typeof (KeyJsonConverter))]
 	public static Key AlternateBackwardKey {
 		get => _alternateBackwardKey;
 		set {
@@ -1343,12 +1336,12 @@ public static partial class Application {
 		}
 	}
 
-	static Key _quitKey = Key.Q | Key.CtrlMask;
+	static Key _quitKey = new (KeyCode.Q | KeyCode.CtrlMask);
 
 	/// <summary>
 	/// Gets or sets the key to quit the application.
 	/// </summary>
-	[SerializableConfigurationProperty (Scope = typeof (SettingsScope)), JsonConverter (typeof (KeyJsonConverter))]
+	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))] [JsonConverter (typeof (KeyJsonConverter))]
 	public static Key QuitKey {
 		get => _quitKey;
 		set {
@@ -1359,6 +1352,7 @@ public static partial class Application {
 			}
 		}
 	}
+
 	static void OnQuitKeyChanged (KeyChangedEventArgs e)
 	{
 		// Duplicate the list so if it changes during enumeration we're safe
@@ -1368,115 +1362,119 @@ public static partial class Application {
 	}
 
 	/// <summary>
-	/// Event fired after a key has been pressed and released.
-	/// <para>Set <see cref="KeyEventEventArgs.Handled"/> to <see langword="true"/> to suppress the event.</para>
+	/// Event fired when the user presses a key. Fired by <see cref="OnKeyDown"/>. 
+	/// <para>
+	/// Set <see cref="Key.Handled"/> to <see langword="true"/> to indicate the key was handled and
+	/// to prevent additional processing.
+	/// </para>
 	/// </summary>
 	/// <remarks>
-	/// All drivers support firing the <see cref="KeyPressed"/> event. Some drivers (Curses)
+	/// All drivers support firing the <see cref="KeyDown"/> event. Some drivers (Curses)
 	/// do not support firing the <see cref="KeyDown"/> and <see cref="KeyUp"/> events.
+	/// <para>
+	/// Fired after <see cref="KeyDown"/> and before <see cref="KeyUp"/>.
+	/// </para>
 	/// </remarks>
-	public static event EventHandler<KeyEventEventArgs> KeyPressed;
+	public static event EventHandler<Key> KeyDown;
 
 	/// <summary>
-	/// Called after a key has been pressed and released. Fires the <see cref="KeyPressed"/> event.
-	/// <para>
-	/// Called for new KeyPressed events before any processing is performed or
-	/// views evaluate. Use for global key handling and/or debugging.
-	/// </para>
+	/// Called by the <see cref="ConsoleDriver"/> when the user presses a key.
+	/// Fires the <see cref="KeyDown"/> event
+	/// then calls <see cref="View.NewKeyDownEvent"/> on all top level views.
+	/// Called after <see cref="OnKeyDown"/> and before <see cref="OnKeyUp"/>.
 	/// </summary>
-	/// <param name="a"></param>
+	/// <remarks>
+	/// Can be used to simulate key press events.
+	/// </remarks>
+	/// <param name="keyEvent"></param>
 	/// <returns><see langword="true"/> if the key was handled.</returns>
-	public static bool OnKeyPressed (KeyEventEventArgs a)
+	public static bool OnKeyDown (Key keyEvent)
 	{
-		KeyPressed?.Invoke (null, a);
-		if (a.Handled) {
+		if (!_initialized) {
 			return true;
 		}
 
-		var chain = _topLevels.ToList ();
-		foreach (var topLevel in chain) {
-			if (topLevel.ProcessHotKey (a.KeyEvent)) {
-				return true;
-			}
-			if (topLevel.Modal)
-				break;
+		KeyDown?.Invoke (null, keyEvent);
+		if (keyEvent.Handled) {
+			return true;
 		}
 
-		foreach (var topLevel in chain) {
-			if (topLevel.ProcessKey (a.KeyEvent)) {
+		foreach (var topLevel in _topLevels.ToList ()) {
+			if (topLevel.NewKeyDownEvent (keyEvent)) {
 				return true;
 			}
-			if (topLevel.Modal)
+			if (topLevel.Modal) {
 				break;
+			}
 		}
 
-		foreach (var topLevel in chain) {
-			// Process the key normally
-			if (topLevel.ProcessColdKey (a.KeyEvent)) {
-				return true;
+		// Invoke any Global KeyBindings
+		foreach (var topLevel in _topLevels.ToList ()) {
+			foreach (var view in topLevel.Subviews.Where (v => v.KeyBindings.TryGet (keyEvent.KeyCode, KeyBindingScope.Application, out var _))) {
+				if (view.KeyBindings.TryGet (keyEvent.KeyCode, KeyBindingScope.Application, out var _)) {
+					keyEvent.Scope = KeyBindingScope.Application;
+					bool? handled = view.OnInvokingKeyBindings (keyEvent);
+					if (handled != null && (bool)handled) {
+						return true;
+					}
+				}
 			}
-			if (topLevel.Modal)
-				break;
 		}
+
 		return false;
 	}
 
 	/// <summary>
-	/// Event fired when a key is pressed (and not yet released). 
+	/// Event fired when the user releases a key. Fired by <see cref="OnKeyUp"/>.
+	/// <para>
+	/// Set <see cref="Key.Handled"/> to <see langword="true"/> to indicate the key was handled and
+	/// to prevent additional processing.
+	/// </para>
 	/// </summary>
 	/// <remarks>
-	/// All drivers support firing the <see cref="KeyPressed"/> event. Some drivers (Curses)
+	/// All drivers support firing the <see cref="KeyDown"/> event. Some drivers (Curses)
 	/// do not support firing the <see cref="KeyDown"/> and <see cref="KeyUp"/> events.
+	/// <para>
+	/// Fired after <see cref="KeyDown"/>.
+	/// </para>
 	/// </remarks>
-	public static event EventHandler<KeyEventEventArgs> KeyDown;
+	public static event EventHandler<Key> KeyUp;
 
 	/// <summary>
-	/// Called when a key is pressed (and not yet released). Fires the <see cref="KeyDown"/> event.
-	/// </summary>
-	/// <param name="a"></param>
-	public static void OnKeyDown (KeyEventEventArgs a)
-	{
-		KeyDown?.Invoke (null, a);
-		var chain = _topLevels.ToList ();
-		foreach (var topLevel in chain) {
-			if (topLevel.OnKeyDown (a.KeyEvent))
-				return;
-			if (topLevel.Modal)
-				break;
-		}
-	}
-
-	/// <summary>
-	/// Event fired when a key is released. 
+	/// Called by the <see cref="ConsoleDriver"/> when the user releases a key.
+	/// Fires the <see cref="KeyUp"/> event
+	/// then calls <see cref="View.NewKeyUpEvent"/> on all top level views.
+	/// Called after <see cref="OnKeyDown"/>.
 	/// </summary>
 	/// <remarks>
-	/// All drivers support firing the <see cref="KeyPressed"/> event. Some drivers (Curses)
-	/// do not support firing the <see cref="KeyDown"/> and <see cref="KeyUp"/> events.
+	/// Can be used to simulate key press events.
 	/// </remarks>
-	public static event EventHandler<KeyEventEventArgs> KeyUp;
-
-	/// <summary>
-	/// Called when a key is released. Fires the <see cref="KeyUp"/> event.
-	/// </summary>
 	/// <param name="a"></param>
-	public static void OnKeyUp (KeyEventEventArgs a)
+	/// <returns><see langword="true"/> if the key was handled.</returns>
+	public static bool OnKeyUp (Key a)
 	{
+		if (!_initialized) {
+			return true;
+		}
+
 		KeyUp?.Invoke (null, a);
-		var chain = _topLevels.ToList ();
-		foreach (var topLevel in chain) {
-			if (topLevel.OnKeyUp (a.KeyEvent))
-				return;
-			if (topLevel.Modal)
+		if (a.Handled) {
+			return true;
+		}
+		foreach (var topLevel in _topLevels.ToList ()) {
+			if (topLevel.NewKeyUpEvent (a)) {
+				return true;
+			}
+			if (topLevel.Modal) {
 				break;
+			}
 		}
-
+		return false;
 	}
-
 	#endregion Keyboard handling
 }
 
 /// <summary>
 /// Event arguments for the <see cref="Application.Iteration"/> event.
 /// </summary>
-public class IterationEventArgs {
-}
+public class IterationEventArgs { }

+ 0 - 2
Terminal.Gui/Clipboard/Clipboard.cs

@@ -58,8 +58,6 @@ public static class Clipboard {
 						Application.Driver.Clipboard.SetClipboardData (value);
 					}
 					_contents = value;
-				} catch (NotSupportedException e) {
-					throw e;
 				} catch (Exception) {
 					_contents = value;
 				}

+ 126 - 0
Terminal.Gui/Configuration/KeyCodeJsonConverter.cs

@@ -0,0 +1,126 @@
+using System;
+using System.Collections.Generic;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Terminal.Gui;
+class KeyCodeJsonConverter : JsonConverter<KeyCode> {
+	public override KeyCode Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+	{
+		if (reader.TokenType == JsonTokenType.StartObject) {
+			KeyCode key = KeyCode.Unknown;
+			Dictionary<string, KeyCode> modifierDict = new Dictionary<string, KeyCode> (comparer: StringComparer.InvariantCultureIgnoreCase) {
+					{ "Shift", KeyCode.ShiftMask },
+					{ "Ctrl", KeyCode.CtrlMask },
+					{ "Alt", KeyCode.AltMask }
+				};
+
+			List<KeyCode> modifiers = new List<KeyCode> ();
+
+			while (reader.Read ()) {
+				if (reader.TokenType == JsonTokenType.EndObject) {
+					break;
+				}
+
+				if (reader.TokenType == JsonTokenType.PropertyName) {
+					string propertyName = reader.GetString ();
+					reader.Read ();
+
+					switch (propertyName.ToLowerInvariant ()) {
+					case "key":
+						if (reader.TokenType == JsonTokenType.String) {
+							if (Enum.TryParse (reader.GetString (), false, out key)) {
+								break;
+							}
+
+							// The enum uses "D0..D9" for the number keys
+							if (Enum.TryParse (reader.GetString ().TrimStart ('D', 'd'), false, out key)) {
+								break;
+							}
+
+							if (key == KeyCode.Unknown || key == KeyCode.Null) {
+								throw new JsonException ($"The value \"{reader.GetString ()}\" is not a valid Key.");
+							}
+
+						} else if (reader.TokenType == JsonTokenType.Number) {
+							try {
+								key = (KeyCode)reader.GetInt32 ();
+							} catch (InvalidOperationException ioe) {
+								throw new JsonException ($"Error parsing Key value: {ioe.Message}", ioe);
+							} catch (FormatException ioe) {
+								throw new JsonException ($"Error parsing Key value: {ioe.Message}", ioe);
+							}
+							break;
+						}
+						break;
+
+					case "modifiers":
+						if (reader.TokenType == JsonTokenType.StartArray) {
+							while (reader.Read ()) {
+								if (reader.TokenType == JsonTokenType.EndArray) {
+									break;
+								}
+								var mod = reader.GetString ();
+								try {
+									modifiers.Add (modifierDict [mod]);
+								} catch (KeyNotFoundException e) {
+									throw new JsonException ($"The value \"{mod}\" is not a valid modifier.", e);
+								}
+							}
+						} else {
+							throw new JsonException ($"Expected an array of modifiers, but got \"{reader.TokenType}\".");
+						}
+						break;
+
+					default:
+						throw new JsonException ($"Unexpected Key property \"{propertyName}\".");
+					}
+				}
+			}
+
+			foreach (var modifier in modifiers) {
+				key |= modifier;
+			}
+
+			return key;
+		}
+		throw new JsonException ($"Unexpected StartObject token when parsing Key: {reader.TokenType}.");
+	}
+
+	public override void Write (Utf8JsonWriter writer, KeyCode value, JsonSerializerOptions options)
+	{
+		writer.WriteStartObject ();
+
+		var keyName = (value & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask & ~KeyCode.AltMask).ToString ();
+		if (keyName != null) {
+			writer.WriteString ("Key", keyName);
+		} else {
+			writer.WriteNumber ("Key", (uint)(value & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask & ~KeyCode.AltMask));
+		}
+
+		Dictionary<string, KeyCode> modifierDict = new Dictionary<string, KeyCode>
+		{
+				{ "Shift", KeyCode.ShiftMask },
+				{ "Ctrl", KeyCode.CtrlMask },
+				{ "Alt", KeyCode.AltMask }
+			    };
+
+		List<string> modifiers = new List<string> ();
+		foreach (var pair in modifierDict) {
+			if ((value & pair.Value) == pair.Value) {
+				modifiers.Add (pair.Key);
+			}
+		}
+
+		if (modifiers.Count > 0) {
+			writer.WritePropertyName ("Modifiers");
+			writer.WriteStartArray ();
+			foreach (var modifier in modifiers) {
+				writer.WriteStringValue (modifier);
+			}
+			writer.WriteEndArray ();
+		}
+
+		writer.WriteEndObject ();
+	}
+}

+ 31 - 110
Terminal.Gui/Configuration/KeyJsonConverter.cs

@@ -1,127 +1,48 @@
 using System;
-using System.Collections.Generic;
 using System.Text.Json;
 using System.Text.Json.Serialization;
 
-namespace Terminal.Gui {
-	class KeyJsonConverter : JsonConverter<Key> {
-		public override Key Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
-		{
-			if (reader.TokenType == JsonTokenType.StartObject) {
-				Key key = Key.Unknown;
-				Dictionary<string, Key> modifierDict = new Dictionary<string, Key> (comparer: StringComparer.InvariantCultureIgnoreCase) {
-					{ "Shift", Key.ShiftMask },
-					{ "Ctrl", Key.CtrlMask },
-					{ "Alt", Key.AltMask }
-				};
-
-				List<Key> modifiers = new List<Key> ();
-
-				while (reader.Read ()) {
-					if (reader.TokenType == JsonTokenType.EndObject) {
-						break;
-					}
-
-					if (reader.TokenType == JsonTokenType.PropertyName) {
-						string propertyName = reader.GetString ();
-						reader.Read ();
-
-						switch (propertyName.ToLowerInvariant ()) {
-						case "key":
-							if (reader.TokenType == JsonTokenType.String) {
-								if (Enum.TryParse (reader.GetString (), false, out key)) {
-									break;
-								}
-								
-								// The enum uses "D0..D9" for the number keys
-								if (Enum.TryParse (reader.GetString ().TrimStart ('D', 'd'), false, out key)) {
-									break;
-								}
+namespace Terminal.Gui;
+class KeyJsonConverter : JsonConverter<Key> {
+	
+	public override Key Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+	{
+		if (reader.TokenType == JsonTokenType.StartObject) {
+			Key key = KeyCode.Unknown;
+			while (reader.Read ()) {
+				if (reader.TokenType == JsonTokenType.EndObject) {
+					break;
+				}
 
-								if (key == Key.Unknown || key == Key.Null) {
-									throw new JsonException ($"The value \"{reader.GetString ()}\" is not a valid Key.");
-								}
+				if (reader.TokenType == JsonTokenType.PropertyName) {
+					string propertyName = reader.GetString ();
+					reader.Read ();
 
-							} else if (reader.TokenType == JsonTokenType.Number) {
-								try {
-									key = (Key)reader.GetInt32 ();
-								} catch (InvalidOperationException ioe) {
-									throw new JsonException ($"Error parsing Key value: {ioe.Message}", ioe);
-								} catch (FormatException ioe) {
-									throw new JsonException ($"Error parsing Key value: {ioe.Message}", ioe);
-								}
+					switch (propertyName?.ToLowerInvariant ()) {
+					case "key":
+						if (reader.TokenType == JsonTokenType.String) {
+							string keyValue = reader.GetString ();
+							if (Key.TryParse (keyValue, out key)) {
 								break;
 							}
-							break;
+							throw new JsonException ($"Error parsing Key: {keyValue}.");
 
-						case "modifiers":
-							if (reader.TokenType == JsonTokenType.StartArray) {
-								while (reader.Read ()) {
-									if (reader.TokenType == JsonTokenType.EndArray) {
-										break;
-									}
-									var mod = reader.GetString ();
-									try {
-										modifiers.Add (modifierDict [mod]);
-									} catch (KeyNotFoundException e) {
-										throw new JsonException ($"The value \"{mod}\" is not a valid modifier.", e);
-									}
-								}
-							} else {
-								throw new JsonException ($"Expected an array of modifiers, but got \"{reader.TokenType}\".");
-							}
-							break;
-
-						default:
-							throw new JsonException ($"Unexpected Key property \"{propertyName}\".");
 						}
+						break;
+					default:
+						throw new JsonException ($"Unexpected Key property \"{propertyName}\".");
 					}
 				}
-
-				foreach (var modifier in modifiers) {
-					key |= modifier;
-				}
-
-				return key;
 			}
-			throw new JsonException ($"Unexpected StartObject token when parsing Key: {reader.TokenType}.");
+			return key;
 		}
+		throw new JsonException ($"Unexpected StartObject token when parsing Key: {reader.TokenType}.");
+	}
 
-		public override void Write (Utf8JsonWriter writer, Key value, JsonSerializerOptions options)
-		{
-			writer.WriteStartObject ();
-
-			var keyName = (value & ~Key.CtrlMask & ~Key.ShiftMask & ~Key.AltMask).ToString ();
-			if (keyName != null) {
-				writer.WriteString ("Key", keyName);
-			} else {
-				writer.WriteNumber ("Key", (uint)(value & ~Key.CtrlMask & ~Key.ShiftMask & ~Key.AltMask));
-			}
-
-			Dictionary<string, Key> modifierDict = new Dictionary<string, Key>
-			{
-				{ "Shift", Key.ShiftMask },
-				{ "Ctrl", Key.CtrlMask },
-				{ "Alt", Key.AltMask }
-			    };
-
-			List<string> modifiers = new List<string> ();
-			foreach (var pair in modifierDict) {
-				if ((value & pair.Value) == pair.Value) {
-					modifiers.Add (pair.Key);
-				}
-			}
-
-			if (modifiers.Count > 0) {
-				writer.WritePropertyName ("Modifiers");
-				writer.WriteStartArray ();
-				foreach (var modifier in modifiers) {
-					writer.WriteStringValue (modifier);
-				}
-				writer.WriteEndArray ();
-			}
-
-			writer.WriteEndObject ();
-		}
+	public override void Write (Utf8JsonWriter writer, Key value, JsonSerializerOptions options)
+	{
+		writer.WriteStartObject ();
+		writer.WriteString ("Key", value.ToString ());
+		writer.WriteEndObject ();
 	}
 }

+ 448 - 32
Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs

@@ -3,11 +3,8 @@
 //
 using System.Text;
 using System;
-using System.Collections.Generic;
 using System.Diagnostics;
-using static Terminal.Gui.ColorScheme;
 using System.Linq;
-using System.Data;
 
 namespace Terminal.Gui;
 
@@ -88,18 +85,9 @@ public abstract class ConsoleDriver {
 	/// The contents of the application output. The driver outputs this buffer to the terminal when <see cref="UpdateScreen"/>
 	/// is called.
 	/// <remarks>
-	/// The format of the array is rows, columns, and 3 values on the last column: Rune, Attribute and Dirty Flag
+	/// The format of the array is rows, columns. The first index is the row, the second index is the column.
 	/// </remarks>
 	/// </summary>
-	//public int [,,] Contents { get; internal set; }
-
-	///// <summary>
-	///// The contents of the application output. The driver outputs this buffer to the terminal when <see cref="UpdateScreen"/>
-	///// is called.
-	///// <remarks>
-	///// The format of the array is rows, columns. The first index is the row, the second index is the column.
-	///// </remarks>
-	///// </summary>
 	public Cell [,] Contents { get; internal set; }
 
 	/// <summary>
@@ -473,39 +461,33 @@ public abstract class ConsoleDriver {
 	#endregion
 
 	#region Mouse and Keyboard
-
 	/// <summary>
-	/// Event fired after a key has been pressed and released.
+	/// Event fired when a key is pressed down. This is a precursor to <see cref="KeyUp"/>.
 	/// </summary>
-	public event EventHandler<KeyEventEventArgs> KeyPressed;
+	public event EventHandler<Key> KeyDown;
 
 	/// <summary>
-	/// Called after a key has been pressed and released. Fires the <see cref="KeyPressed"/> event.
+	/// Called when a key is pressed down. Fires the <see cref="KeyDown"/> event. This is a precursor to <see cref="OnKeyUp"/>.
 	/// </summary>
 	/// <param name="a"></param>
-	public void OnKeyPressed (KeyEventEventArgs a) => KeyPressed?.Invoke (this, a);
+	public void OnKeyDown (Key a) => KeyDown?.Invoke (this, a);
 
 	/// <summary>
-	/// Event fired when a key is released.
+	/// Event fired when a key is released. 
 	/// </summary>
-	public event EventHandler<KeyEventEventArgs> KeyUp;
+	/// <remarks>
+	/// Drivers that do not support key release events will fire this event after <see cref="KeyDown"/> processing is complete.
+	/// </remarks>
+	public event EventHandler<Key> KeyUp;
 
 	/// <summary>
 	/// Called when a key is released. Fires the <see cref="KeyUp"/> event.
 	/// </summary>
+	/// <remarks>
+	/// Drivers that do not support key release events will calls this method after <see cref="OnKeyDown"/> processing is complete.
+	/// </remarks>
 	/// <param name="a"></param>
-	public void OnKeyUp (KeyEventEventArgs a) => KeyUp?.Invoke (this, a);
-
-	/// <summary>
-	/// Event fired when a key is pressed.
-	/// </summary>
-	public event EventHandler<KeyEventEventArgs> KeyDown;
-
-	/// <summary>
-	/// Called when a key is pressed. Fires the <see cref="KeyDown"/> event.
-	/// </summary>
-	/// <param name="a"></param>
-	public void OnKeyDown (KeyEventEventArgs a) => KeyDown?.Invoke (this, a);
+	public void OnKeyUp (Key a) => KeyUp?.Invoke (this, a);
 
 	/// <summary>
 	/// Event fired when a mouse event occurs.
@@ -651,3 +633,437 @@ public enum CursorVisibility {
 	/// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Block"/></remarks>
 	BoxFix = 0x02020164,
 }
+
+
+/// <summary>
+/// The <see cref="KeyCode"/> enumeration encodes key information from <see cref="ConsoleDriver"/>s and provides a consistent
+/// way for application code to specify keys and receive key events. 
+/// <para>
+/// The <see cref="Key"/> class provides a higher-level abstraction, with helper methods and properties for common
+/// operations. For example, <see cref="Key.IsAlt"/> and <see cref="Key.IsCtrl"/> provide a convenient way
+/// to check whether the Alt or Ctrl modifier keys were pressed when a key was pressed.
+/// </para>
+/// </summary>
+/// <remarks>
+/// <para>
+/// Lowercase alpha keys are encoded as values between 65 and 90 corresponding to the un-shifted A to Z keys on a keyboard. Enum values
+/// are provided for these (e.g. <see cref="KeyCode.A"/>, <see cref="KeyCode.B"/>, etc.). Even though the values are the same as the ASCII
+/// values for uppercase characters, these enum values represent *lowercase*, un-shifted characters.
+/// TODO: Strongly consider renaming these from .A to .Z to .A_Lowercase to .Z_Lowercase (or .a to .z).
+/// </para>
+/// <para>
+/// Numeric keys are the values between 48 and 57 corresponding to 0 to 9 (e.g. <see cref="KeyCode.D0"/>, <see cref="KeyCode.D1"/>, etc.).
+/// </para>
+/// <para>
+/// The shift modifiers (<see cref="KeyCode.ShiftMask"/>, <see cref="KeyCode.CtrlMask"/>, and <see cref="KeyCode.AltMask"/>) can be combined (with logical or)
+/// with the other key codes to represent shifted keys. For example, the <see cref="KeyCode.A"/> enum value represents the un-shifted 'a' key, while
+/// <see cref="KeyCode.ShiftMask"/> | <see cref="KeyCode.A"/> represents the 'A' key (shifted 'a' key). Likewise, <see cref="KeyCode.AltMask"/> | <see cref="KeyCode.A"/>
+/// represents the 'Alt+A' key combination.
+/// </para>
+/// <para>
+/// All other keys that produce a printable character are encoded as the Unicode value of the character. For example, the <see cref="KeyCode"/>
+/// for the '!' character is 33, which is the Unicode value for '!'. Likewise, `â` is 226, `Â` is 194, etc.
+/// </para>
+/// <para>
+/// If the <see cref="SpecialMask"/> is set, then the value is that of the special mask,
+/// otherwise, the value is the one of the lower bits (as extracted by <see cref="CharMask"/>).
+/// </para>
+/// </remarks>
+[Flags]
+public enum KeyCode : uint {
+	/// <summary>
+	/// Mask that indicates that this is a character value, values outside this range
+	/// indicate special characters like Alt-key combinations or special keys on the
+	/// keyboard like function keys, arrows keys and so on.
+	/// </summary>
+	CharMask = 0xfffff,
+
+	/// <summary>
+	/// If the <see cref="SpecialMask"/> is set, then the value is that of the special mask,
+	/// otherwise, the value is the one of the lower bits (as extracted by <see cref="CharMask"/>).
+	/// </summary>
+	SpecialMask = 0xfff00000,
+
+	/// <summary>
+	/// The key code representing null or empty
+	/// </summary>
+	Null = '\0',
+
+	/// <summary>
+	/// Backspace key.
+	/// </summary>
+	Backspace = 8,
+
+	/// <summary>
+	/// The key code for the tab key (forwards tab key).
+	/// </summary>
+	Tab = 9,
+
+	/// <summary>
+	/// The key code for the return key.
+	/// </summary>
+	Enter = '\n',
+
+	/// <summary>
+	/// The key code for the clear key.
+	/// </summary>
+	Clear = 12,
+
+	/// <summary>
+	/// The key code for the Shift key.
+	/// </summary>
+	ShiftKey = 16,
+
+	/// <summary>
+	/// The key code for the Ctrl key.
+	/// </summary>
+	CtrlKey = 17,
+
+	/// <summary>
+	/// The key code for the Alt key.
+	/// </summary>
+	AltKey = 18,
+
+	/// <summary>
+	/// The key code for the CapsLock key.
+	/// </summary>
+	CapsLock = 20,
+
+	///// <summary>
+	///// The key code for the NumLock key.
+	///// </summary>
+	//NumLock = 144,
+
+	///// <summary>
+	///// The key code for the ScrollLock key.
+	///// </summary>
+	//ScrollLock = 145,
+
+	/// <summary>
+	/// The key code for the escape key.
+	/// </summary>
+	Esc = 27,
+
+	/// <summary>
+	/// The key code for the space bar key.
+	/// </summary>
+	Space = 32,
+
+	/// <summary>
+	/// Digit 0.
+	/// </summary>
+	D0 = 48,
+	/// <summary>
+	/// Digit 1.
+	/// </summary>
+	D1,
+	/// <summary>
+	/// Digit 2.
+	/// </summary>
+	D2,
+	/// <summary>
+	/// Digit 3.
+	/// </summary>
+	D3,
+	/// <summary>
+	/// Digit 4.
+	/// </summary>
+	D4,
+	/// <summary>
+	/// Digit 5.
+	/// </summary>
+	D5,
+	/// <summary>
+	/// Digit 6.
+	/// </summary>
+	D6,
+	/// <summary>
+	/// Digit 7.
+	/// </summary>
+	D7,
+	/// <summary>
+	/// Digit 8.
+	/// </summary>
+	D8,
+	/// <summary>
+	/// Digit 9.
+	/// </summary>
+	D9,
+
+	/// <summary>
+	/// The key code for the user pressing Shift-A
+	/// </summary>
+	A = 65,
+	/// <summary>
+	/// The key code for the user pressing Shift-B
+	/// </summary>
+	B,
+	/// <summary>
+	/// The key code for the user pressing Shift-C
+	/// </summary>
+	C,
+	/// <summary>
+	/// The key code for the user pressing Shift-D
+	/// </summary>
+	D,
+	/// <summary>
+	/// The key code for the user pressing Shift-E
+	/// </summary>
+	E,
+	/// <summary>
+	/// The key code for the user pressing Shift-F
+	/// </summary>
+	F,
+	/// <summary>
+	/// The key code for the user pressing Shift-G
+	/// </summary>
+	G,
+	/// <summary>
+	/// The key code for the user pressing Shift-H
+	/// </summary>
+	H,
+	/// <summary>
+	/// The key code for the user pressing Shift-I
+	/// </summary>
+	I,
+	/// <summary>
+	/// The key code for the user pressing Shift-J
+	/// </summary>
+	J,
+	/// <summary>
+	/// The key code for the user pressing Shift-K
+	/// </summary>
+	K,
+	/// <summary>
+	/// The key code for the user pressing Shift-L
+	/// </summary>
+	L,
+	/// <summary>
+	/// The key code for the user pressing Shift-M
+	/// </summary>
+	M,
+	/// <summary>
+	/// The key code for the user pressing Shift-N
+	/// </summary>
+	N,
+	/// <summary>
+	/// The key code for the user pressing Shift-O
+	/// </summary>
+	O,
+	/// <summary>
+	/// The key code for the user pressing Shift-P
+	/// </summary>
+	P,
+	/// <summary>
+	/// The key code for the user pressing Shift-Q
+	/// </summary>
+	Q,
+	/// <summary>
+	/// The key code for the user pressing Shift-R
+	/// </summary>
+	R,
+	/// <summary>
+	/// The key code for the user pressing Shift-S
+	/// </summary>
+	S,
+	/// <summary>
+	/// The key code for the user pressing Shift-T
+	/// </summary>
+	T,
+	/// <summary>
+	/// The key code for the user pressing Shift-U
+	/// </summary>
+	U,
+	/// <summary>
+	/// The key code for the user pressing Shift-V
+	/// </summary>
+	V,
+	/// <summary>
+	/// The key code for the user pressing Shift-W
+	/// </summary>
+	W,
+	/// <summary>
+	/// The key code for the user pressing Shift-X
+	/// </summary>
+	X,
+	/// <summary>
+	/// The key code for the user pressing Shift-Y
+	/// </summary>
+	Y,
+	/// <summary>
+	/// The key code for the user pressing Shift-Z
+	/// </summary>
+	Z,
+	/// <summary>
+	/// The key code for the user pressing A
+	/// </summary>
+	Delete = 127,
+
+	/// <summary>
+	/// When this value is set, the Key encodes the sequence Shift-KeyValue.
+	/// </summary>
+	ShiftMask = 0x10000000,
+
+	/// <summary>
+	///   When this value is set, the Key encodes the sequence Alt-KeyValue.
+	///   And the actual value must be extracted by removing the AltMask.
+	/// </summary>
+	AltMask = 0x80000000,
+
+	/// <summary>
+	///   When this value is set, the Key encodes the sequence Ctrl-KeyValue.
+	///   And the actual value must be extracted by removing the CtrlMask.
+	/// </summary>
+	CtrlMask = 0x40000000,
+
+	/// <summary>
+	/// Cursor up key
+	/// </summary>
+	CursorUp = 0x100000,
+	/// <summary>
+	/// Cursor down key.
+	/// </summary>
+	CursorDown,
+	/// <summary>
+	/// Cursor left key.
+	/// </summary>
+	CursorLeft,
+	/// <summary>
+	/// Cursor right key.
+	/// </summary>
+	CursorRight,
+	/// <summary>
+	/// Page Up key.
+	/// </summary>
+	PageUp,
+	/// <summary>
+	/// Page Down key.
+	/// </summary>
+	PageDown,
+	/// <summary>
+	/// Home key.
+	/// </summary>
+	Home,
+	/// <summary>
+	/// End key.
+	/// </summary>
+	End,
+
+	/// <summary>
+	/// Insert character key.
+	/// </summary>
+	InsertChar,
+
+	/// <summary>
+	/// Delete character key.
+	/// </summary>
+	DeleteChar,
+
+	/// <summary>
+	/// Print screen character key.
+	/// </summary>
+	PrintScreen,
+
+	/// <summary>
+	/// F1 key.
+	/// </summary>
+	F1,
+	/// <summary>
+	/// F2 key.
+	/// </summary>
+	F2,
+	/// <summary>
+	/// F3 key.
+	/// </summary>
+	F3,
+	/// <summary>
+	/// F4 key.
+	/// </summary>
+	F4,
+	/// <summary>
+	/// F5 key.
+	/// </summary>
+	F5,
+	/// <summary>
+	/// F6 key.
+	/// </summary>
+	F6,
+	/// <summary>
+	/// F7 key.
+	/// </summary>
+	F7,
+	/// <summary>
+	/// F8 key.
+	/// </summary>
+	F8,
+	/// <summary>
+	/// F9 key.
+	/// </summary>
+	F9,
+	/// <summary>
+	/// F10 key.
+	/// </summary>
+	F10,
+	/// <summary>
+	/// F11 key.
+	/// </summary>
+	F11,
+	/// <summary>
+	/// F12 key.
+	/// </summary>
+	F12,
+	/// <summary>
+	/// F13 key.
+	/// </summary>
+	F13,
+	/// <summary>
+	/// F14 key.
+	/// </summary>
+	F14,
+	/// <summary>
+	/// F15 key.
+	/// </summary>
+	F15,
+	/// <summary>
+	/// F16 key.
+	/// </summary>
+	F16,
+	/// <summary>
+	/// F17 key.
+	/// </summary>
+	F17,
+	/// <summary>
+	/// F18 key.
+	/// </summary>
+	F18,
+	/// <summary>
+	/// F19 key.
+	/// </summary>
+	F19,
+	/// <summary>
+	/// F20 key.
+	/// </summary>
+	F20,
+	/// <summary>
+	/// F21 key.
+	/// </summary>
+	F21,
+	/// <summary>
+	/// F22 key.
+	/// </summary>
+	F22,
+	/// <summary>
+	/// F23 key.
+	/// </summary>
+	F23,
+	/// <summary>
+	/// F24 key.
+	/// </summary>
+	F24,
+
+	/// <summary>
+	/// A key with an unknown mapping was raised.
+	/// </summary>
+	Unknown
+}
+

+ 601 - 0
Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs

@@ -0,0 +1,601 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+
+namespace Terminal.Gui.ConsoleDrivers {
+	/// <summary>
+	/// Helper class to handle the scan code and virtual key from a <see cref="ConsoleKey"/>.
+	/// </summary>
+	public static class ConsoleKeyMapping {
+		class ScanCodeMapping : IEquatable<ScanCodeMapping> {
+			public uint ScanCode;
+			public uint VirtualKey;
+			public ConsoleModifiers Modifiers;
+			public uint UnicodeChar;
+
+			public ScanCodeMapping (uint scanCode, uint virtualKey, ConsoleModifiers modifiers, uint unicodeChar)
+			{
+				ScanCode = scanCode;
+				VirtualKey = virtualKey;
+				Modifiers = modifiers;
+				UnicodeChar = unicodeChar;
+			}
+
+			public bool Equals (ScanCodeMapping other)
+			{
+				return ScanCode.Equals (other.ScanCode) &&
+					VirtualKey.Equals (other.VirtualKey) &&
+					Modifiers.Equals (other.Modifiers) &&
+					UnicodeChar.Equals (other.UnicodeChar);
+			}
+		}
+
+		static ConsoleModifiers GetModifiers (ConsoleModifiers modifiers)
+		{
+			if (modifiers.HasFlag (ConsoleModifiers.Shift)
+			&& !modifiers.HasFlag (ConsoleModifiers.Alt)
+			&& !modifiers.HasFlag (ConsoleModifiers.Control)) {
+				return ConsoleModifiers.Shift;
+			} else if (modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) {
+				return modifiers;
+			}
+
+			return 0;
+		}
+
+		static ScanCodeMapping GetScanCode (string propName, uint keyValue, ConsoleModifiers modifiers)
+		{
+			switch (propName) {
+			case "UnicodeChar":
+				var sCode = scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyValue && e.Modifiers == modifiers);
+				if (sCode == null && modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) {
+					return scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyValue && e.Modifiers == 0);
+				}
+				return sCode;
+			case "VirtualKey":
+				sCode = scanCodes.FirstOrDefault ((e) => e.VirtualKey == keyValue && e.Modifiers == modifiers);
+				if (sCode == null && modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) {
+					return scanCodes.FirstOrDefault ((e) => e.VirtualKey == keyValue && e.Modifiers == 0);
+				}
+				return sCode;
+			}
+
+			return null;
+		}
+
+		/// <summary>
+		/// Gets the <see cref="ConsoleKey"/> from the provided <see cref="KeyCode"/>.
+		/// </summary>
+		/// <param name="key"></param>
+		/// <returns></returns>
+		public static ConsoleKeyInfo GetConsoleKeyFromKey (KeyCode key)
+		{
+			var mod = new ConsoleModifiers ();
+			if (key.HasFlag (KeyCode.ShiftMask)) {
+				mod |= ConsoleModifiers.Shift;
+			}
+			if (key.HasFlag (KeyCode.AltMask)) {
+				mod |= ConsoleModifiers.Alt;
+			}
+			if (key.HasFlag (KeyCode.CtrlMask)) {
+				mod |= ConsoleModifiers.Control;
+			}
+			return GetConsoleKeyFromKey ((uint)(key & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask & ~KeyCode.AltMask), mod, out _);
+		}
+
+		/// <summary>
+		/// Get the <see cref="ConsoleKeyInfo"/> from a unicode character and modifiers (e.g. (Key)'a' and (Key)Key.CtrlMask).
+		/// </summary>
+		/// <param name="keyValue">The key as a unicode codepoint.</param>
+		/// <param name="modifiers">The modifier keys.</param>
+		/// <param name="scanCode">The resulting scan code.</param>
+		/// <returns>The <see cref="ConsoleKeyInfo"/>.</returns>
+		public static ConsoleKeyInfo GetConsoleKeyFromKey (uint keyValue, ConsoleModifiers modifiers, out uint scanCode)
+		{
+			scanCode = 0;
+			uint outputChar = keyValue;
+			if (keyValue == 0) {
+				return new ConsoleKeyInfo ((char)keyValue, ConsoleKey.None, modifiers.HasFlag (ConsoleModifiers.Shift),
+					modifiers.HasFlag (ConsoleModifiers.Alt), modifiers.HasFlag (ConsoleModifiers.Control));
+			}
+
+			uint consoleKey = (uint)MapKeyToConsoleKey ((KeyCode)keyValue, modifiers, out bool mappable);
+			if (mappable) {
+				var mod = GetModifiers (modifiers);
+				var scode = GetScanCode ("UnicodeChar", keyValue, mod);
+				if (scode != null) {
+					consoleKey = scode.VirtualKey;
+					scanCode = scode.ScanCode;
+					outputChar = scode.UnicodeChar;
+				} else {
+					// If the consoleKey is < 255, retain the lower 8 bits of the key value and set the upper bits to 0xff.
+					// This is a shifted value that will be used by the GetKeyCharFromConsoleKey to do the correct action
+					// because keyValue maybe a UnicodeChar or a ConsoleKey, e.g. for PageUp is passed the ConsoleKey.PageUp
+					consoleKey = consoleKey < 0xff ? consoleKey & 0xff | 0xff << 8 : consoleKey;
+					outputChar = GetKeyCharFromConsoleKey (consoleKey, modifiers, out consoleKey, out scanCode);
+				}
+			} else {
+				var mod = GetModifiers (modifiers);
+				var scode = GetScanCode ("VirtualKey", consoleKey, mod);
+				if (scode != null) {
+					consoleKey = scode.VirtualKey;
+					scanCode = scode.ScanCode;
+					outputChar = scode.UnicodeChar;
+				}
+			}
+
+			return new ConsoleKeyInfo ((char)outputChar, (ConsoleKey)consoleKey, modifiers.HasFlag (ConsoleModifiers.Shift),
+					modifiers.HasFlag (ConsoleModifiers.Alt), modifiers.HasFlag (ConsoleModifiers.Control));
+		}
+
+		/// <summary>
+		/// Get the output character from the <see cref="ConsoleKey"/>, the correct <see cref="ConsoleKey"/>
+		/// and the scan code used on <see cref="WindowsDriver"/>.
+		/// </summary>
+		/// <param name="unicodeChar">The unicode character.</param>
+		/// <param name="modifiers">The modifiers keys.</param>
+		/// <param name="consoleKey">The resulting console key.</param>
+		/// <param name="scanCode">The resulting scan code.</param>
+		/// <returns>The output character or the <paramref name="consoleKey"/>.</returns>
+		static uint GetKeyCharFromConsoleKey (uint unicodeChar, ConsoleModifiers modifiers, out uint consoleKey, out uint scanCode)
+		{
+			uint decodedChar = unicodeChar >> 8 == 0xff ? unicodeChar & 0xff : unicodeChar;
+			uint keyChar = decodedChar;
+			consoleKey = 0;
+			var mod = GetModifiers (modifiers);
+			scanCode = 0;
+			var scode = unicodeChar != 0 && unicodeChar >> 8 != 0xff ? GetScanCode ("VirtualKey", decodedChar, mod) : null;
+			if (scode != null) {
+				consoleKey = scode.VirtualKey;
+				keyChar = scode.UnicodeChar;
+				scanCode = scode.ScanCode;
+			}
+			if (scode == null) {
+				scode = unicodeChar != 0 ? GetScanCode ("UnicodeChar", decodedChar, mod) : null;
+				if (scode != null) {
+					consoleKey = scode.VirtualKey;
+					keyChar = scode.UnicodeChar;
+					scanCode = scode.ScanCode;
+				}
+			}
+			if (decodedChar != 0 && scanCode == 0 && char.IsLetter ((char)decodedChar)) {
+				string stFormD = ((char)decodedChar).ToString ().Normalize (System.Text.NormalizationForm.FormD);
+				for (int i = 0; i < stFormD.Length; i++) {
+					var uc = CharUnicodeInfo.GetUnicodeCategory (stFormD [i]);
+					if (uc != UnicodeCategory.NonSpacingMark && uc != UnicodeCategory.OtherLetter) {
+						consoleKey = char.ToUpper (stFormD [i]);
+						scode = GetScanCode ("VirtualKey", char.ToUpper (stFormD [i]), 0);
+						if (scode != null) {
+							scanCode = scode.ScanCode;
+						}
+					}
+				}
+			}
+
+			return keyChar;
+		}
+
+		/// <summary>
+		/// Maps a unicode character (e.g. (Key)'a') to a uint representing a <see cref="ConsoleKey"/>.
+		/// </summary>
+		/// <param name="keyValue">The key value.</param>
+		/// <param name="modifiers">The modifiers keys.</param>
+		/// <param name="isMappable">
+		/// <see langword="true"/> means the return value can be mapped to a valid unicode character.
+		/// <see langword="false"/> means the return value is in the ConsoleKey enum.
+		/// </param>
+		/// <returns>The <see cref="ConsoleKey"/> or the <paramref name="keyValue"/>.</returns>
+		public static ConsoleKey MapKeyToConsoleKey (KeyCode keyValue, ConsoleModifiers modifiers, out bool isMappable)
+		{
+			isMappable = false;
+
+			switch (keyValue) {
+			case KeyCode.Delete:
+				return ConsoleKey.Delete;
+			case KeyCode.CursorUp:
+				return ConsoleKey.UpArrow;
+			case KeyCode.CursorDown:
+				return ConsoleKey.DownArrow;
+			case KeyCode.CursorLeft:
+				return ConsoleKey.LeftArrow;
+			case KeyCode.CursorRight:
+				return ConsoleKey.RightArrow;
+			case KeyCode.PageUp:
+				return ConsoleKey.PageUp;
+			case KeyCode.PageDown:
+				return ConsoleKey.PageDown;
+			case KeyCode.Home:
+				return ConsoleKey.Home;
+			case KeyCode.End:
+				return ConsoleKey.End;
+			case KeyCode.InsertChar:
+				return ConsoleKey.Insert;
+			case KeyCode.DeleteChar:
+				return ConsoleKey.Delete;
+			case KeyCode.F1:
+				return ConsoleKey.F1;
+			case KeyCode.F2:
+				return ConsoleKey.F2;
+			case KeyCode.F3:
+				return ConsoleKey.F3;
+			case KeyCode.F4:
+				return ConsoleKey.F4;
+			case KeyCode.F5:
+				return ConsoleKey.F5;
+			case KeyCode.F6:
+				return ConsoleKey.F6;
+			case KeyCode.F7:
+				return ConsoleKey.F7;
+			case KeyCode.F8:
+				return ConsoleKey.F8;
+			case KeyCode.F9:
+				return ConsoleKey.F9;
+			case KeyCode.F10:
+				return ConsoleKey.F10;
+			case KeyCode.F11:
+				return ConsoleKey.F11;
+			case KeyCode.F12:
+				return ConsoleKey.F12;
+			case KeyCode.F13:
+				return ConsoleKey.F13;
+			case KeyCode.F14:
+				return ConsoleKey.F14;
+			case KeyCode.F15:
+				return ConsoleKey.F15;
+			case KeyCode.F16:
+				return ConsoleKey.F16;
+			case KeyCode.F17:
+				return ConsoleKey.F17;
+			case KeyCode.F18:
+				return ConsoleKey.F18;
+			case KeyCode.F19:
+				return ConsoleKey.F19;
+			case KeyCode.F20:
+				return ConsoleKey.F20;
+			case KeyCode.F21:
+				return ConsoleKey.F21;
+			case KeyCode.F22:
+				return ConsoleKey.F22;
+			case KeyCode.F23:
+				return ConsoleKey.F23;
+			case KeyCode.F24:
+				return ConsoleKey.F24;
+			case KeyCode.Tab | KeyCode.ShiftMask:
+				return ConsoleKey.Tab;
+			case KeyCode.Unknown:
+				isMappable = true;
+				return 0;
+			}
+
+			isMappable = true;
+
+			if (modifiers == ConsoleModifiers.Shift && keyValue - 32 is >= KeyCode.A and <= KeyCode.Z) {
+				return (ConsoleKey)(keyValue - 32);
+			} else if (modifiers == ConsoleModifiers.None && keyValue is >= KeyCode.A and <= KeyCode.Z) {
+				return (ConsoleKey)(keyValue + 32);
+			}
+			if (modifiers == ConsoleModifiers.Shift && keyValue - 32 is >= (KeyCode)'À' and <= (KeyCode)'Ý') {
+				return (ConsoleKey)(keyValue - 32);
+			} else if (modifiers == ConsoleModifiers.None && keyValue is >= (KeyCode)'À' and <= (KeyCode)'Ý') {
+				return (ConsoleKey)(keyValue + 32);
+			}
+
+			return (ConsoleKey)keyValue;
+		}
+
+		/// <summary>
+		/// Maps a <see cref="ConsoleKey"/> to a <see cref="KeyCode"/>.
+		/// </summary>
+		/// <param name="consoleKey">The console key.</param>
+		/// <param name="isMappable">If <see langword="true"/> is mapped to a valid character, otherwise <see langword="false"/>.</param>
+		/// <returns>The <see cref="KeyCode"/> or the <paramref name="consoleKey"/>.</returns>
+		public static KeyCode MapConsoleKeyToKey (ConsoleKey consoleKey, out bool isMappable)
+		{
+			isMappable = false;
+
+			switch (consoleKey) {
+			case ConsoleKey.Delete:
+				return KeyCode.Delete;
+			case ConsoleKey.UpArrow:
+				return KeyCode.CursorUp;
+			case ConsoleKey.DownArrow:
+				return KeyCode.CursorDown;
+			case ConsoleKey.LeftArrow:
+				return KeyCode.CursorLeft;
+			case ConsoleKey.RightArrow:
+				return KeyCode.CursorRight;
+			case ConsoleKey.PageUp:
+				return KeyCode.PageUp;
+			case ConsoleKey.PageDown:
+				return KeyCode.PageDown;
+			case ConsoleKey.Home:
+				return KeyCode.Home;
+			case ConsoleKey.End:
+				return KeyCode.End;
+			case ConsoleKey.Insert:
+				return KeyCode.InsertChar;
+			case ConsoleKey.F1:
+				return KeyCode.F1;
+			case ConsoleKey.F2:
+				return KeyCode.F2;
+			case ConsoleKey.F3:
+				return KeyCode.F3;
+			case ConsoleKey.F4:
+				return KeyCode.F4;
+			case ConsoleKey.F5:
+				return KeyCode.F5;
+			case ConsoleKey.F6:
+				return KeyCode.F6;
+			case ConsoleKey.F7:
+				return KeyCode.F7;
+			case ConsoleKey.F8:
+				return KeyCode.F8;
+			case ConsoleKey.F9:
+				return KeyCode.F9;
+			case ConsoleKey.F10:
+				return KeyCode.F10;
+			case ConsoleKey.F11:
+				return KeyCode.F11;
+			case ConsoleKey.F12:
+				return KeyCode.F12;
+			case ConsoleKey.F13:
+				return KeyCode.F13;
+			case ConsoleKey.F14:
+				return KeyCode.F14;
+			case ConsoleKey.F15:
+				return KeyCode.F15;
+			case ConsoleKey.F16:
+				return KeyCode.F16;
+			case ConsoleKey.F17:
+				return KeyCode.F17;
+			case ConsoleKey.F18:
+				return KeyCode.F18;
+			case ConsoleKey.F19:
+				return KeyCode.F19;
+			case ConsoleKey.F20:
+				return KeyCode.F20;
+			case ConsoleKey.F21:
+				return KeyCode.F21;
+			case ConsoleKey.F22:
+				return KeyCode.F22;
+			case ConsoleKey.F23:
+				return KeyCode.F23;
+			case ConsoleKey.F24:
+				return KeyCode.F24;
+			case ConsoleKey.Tab:
+				return KeyCode.Tab;
+			}
+			isMappable = true;
+
+			if (consoleKey is >= ConsoleKey.A and <= ConsoleKey.Z) {
+				return (KeyCode)(consoleKey + 32);
+			}
+
+			return (KeyCode)consoleKey;
+		}
+
+		/// <summary>
+		/// Maps a <see cref="ConsoleKeyInfo"/> to a <see cref="KeyCode"/>.
+		/// </summary>
+		/// <param name="keyInfo">The console key info.</param>
+		/// <param name="key">The key.</param>
+		/// <returns>The <see cref="KeyCode"/> with <see cref="ConsoleModifiers"/> or the <paramref name="key"/></returns>
+		public static KeyCode MapKeyModifiers (ConsoleKeyInfo keyInfo, KeyCode key)
+		{
+			var keyMod = new KeyCode ();
+			if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0) {
+				keyMod = KeyCode.ShiftMask;
+			}
+			if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0) {
+				keyMod |= KeyCode.CtrlMask;
+			}
+			if ((keyInfo.Modifiers & ConsoleModifiers.Alt) != 0) {
+				keyMod |= KeyCode.AltMask;
+			}
+
+			return keyMod != KeyCode.Null ? keyMod | key : key;
+		}
+
+		static HashSet<ScanCodeMapping> scanCodes = new HashSet<ScanCodeMapping> {
+			new ScanCodeMapping (1, 27, 0, 27), // Escape
+			new ScanCodeMapping (1, 27, ConsoleModifiers.Shift, 27),
+			new ScanCodeMapping (2, 49, 0, 49), // D1
+			new ScanCodeMapping (2, 49, ConsoleModifiers.Shift, 33),
+			new ScanCodeMapping (3, 50, 0, 50), // D2
+			new ScanCodeMapping (3, 50, ConsoleModifiers.Shift, 34),
+			new ScanCodeMapping (3, 50, ConsoleModifiers.Alt | ConsoleModifiers.Control, 64),
+			new ScanCodeMapping (4, 51, 0, 51), // D3
+			new ScanCodeMapping (4, 51, ConsoleModifiers.Shift, 35),
+			new ScanCodeMapping (4, 51, ConsoleModifiers.Alt | ConsoleModifiers.Control, 163),
+			new ScanCodeMapping (5, 52, 0, 52), // D4
+			new ScanCodeMapping (5, 52, ConsoleModifiers.Shift, 36),
+			new ScanCodeMapping (5, 52, ConsoleModifiers.Alt | ConsoleModifiers.Control, 167),
+			new ScanCodeMapping (6, 53, 0, 53), // D5
+			new ScanCodeMapping (6, 53, ConsoleModifiers.Shift, 37),
+			new ScanCodeMapping (6, 53, ConsoleModifiers.Alt | ConsoleModifiers.Control, 8364),
+			new ScanCodeMapping (7, 54, 0, 54), // D6
+			new ScanCodeMapping (7, 54, ConsoleModifiers.Shift, 38),
+			new ScanCodeMapping (8, 55, 0, 55), // D7
+			new ScanCodeMapping (8, 55, ConsoleModifiers.Shift, 47),
+			new ScanCodeMapping (8, 55, ConsoleModifiers.Alt | ConsoleModifiers.Control, 123),
+			new ScanCodeMapping (9, 56, 0, 56), // D8
+			new ScanCodeMapping (9, 56, ConsoleModifiers.Shift, 40),
+			new ScanCodeMapping (9, 56, ConsoleModifiers.Alt | ConsoleModifiers.Control, 91),
+			new ScanCodeMapping (10, 57, 0, 57), // D9
+			new ScanCodeMapping (10, 57, ConsoleModifiers.Shift, 41),
+			new ScanCodeMapping (10, 57, ConsoleModifiers.Alt | ConsoleModifiers.Control, 93),
+			new ScanCodeMapping (11, 48, 0, 48), // D0
+			new ScanCodeMapping (11, 48, ConsoleModifiers.Shift, 61),
+			new ScanCodeMapping (11, 48, ConsoleModifiers.Alt | ConsoleModifiers.Control, 125),
+			new ScanCodeMapping (12, 219, 0, 39), // Oem4
+			new ScanCodeMapping (12, 219, ConsoleModifiers.Shift, 63),
+			new ScanCodeMapping (13, 221, 0, 171), // Oem6
+			new ScanCodeMapping (13, 221, ConsoleModifiers.Shift, 187),
+			new ScanCodeMapping (14, 8, 0, 8), // Backspace
+			new ScanCodeMapping (14, 8, ConsoleModifiers.Shift, 8),
+			new ScanCodeMapping (15, 9, 0, 9), // Tab
+			new ScanCodeMapping (15, 9, ConsoleModifiers.Shift, 15),
+			new ScanCodeMapping (16, 81, 0, 113), // Q
+			new ScanCodeMapping (16, 81, ConsoleModifiers.Shift, 81),
+			new ScanCodeMapping (17, 87, 0, 119), // W
+			new ScanCodeMapping (17, 87, ConsoleModifiers.Shift, 87),
+			new ScanCodeMapping (18, 69, 0, 101), // E
+			new ScanCodeMapping (18, 69, ConsoleModifiers.Shift, 69),
+			new ScanCodeMapping (19, 82, 0, 114), // R
+			new ScanCodeMapping (19, 82, ConsoleModifiers.Shift, 82),
+			new ScanCodeMapping (20, 84, 0, 116), // T
+			new ScanCodeMapping (20, 84, ConsoleModifiers.Shift, 84),
+			new ScanCodeMapping (21, 89, 0, 121), // Y
+			new ScanCodeMapping (21, 89, ConsoleModifiers.Shift, 89),
+			new ScanCodeMapping (22, 85, 0, 117), // U
+			new ScanCodeMapping (22, 85, ConsoleModifiers.Shift, 85),
+			new ScanCodeMapping (23, 73, 0, 105), // I
+			new ScanCodeMapping (23, 73, ConsoleModifiers.Shift, 73),
+			new ScanCodeMapping (24, 79, 0, 111), // O
+			new ScanCodeMapping (24, 79, ConsoleModifiers.Shift, 79),
+			new ScanCodeMapping (25, 80, 0, 112), // P
+			new ScanCodeMapping (25, 80, ConsoleModifiers.Shift, 80),
+			new ScanCodeMapping (26, 187, 0, 43), // OemPlus
+			new ScanCodeMapping (26, 187, ConsoleModifiers.Shift, 42),
+			new ScanCodeMapping (26, 187, ConsoleModifiers.Alt | ConsoleModifiers.Control, 168),
+			new ScanCodeMapping (27, 186, 0, 180), // Oem1
+			new ScanCodeMapping (27, 186, ConsoleModifiers.Shift, 96),
+			new ScanCodeMapping (28, 13, 0, 13), // Enter
+			new ScanCodeMapping (28, 13, ConsoleModifiers.Shift, 13),
+			new ScanCodeMapping (29, 17, 0, 0), // Control
+			new ScanCodeMapping (29, 17, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (scanCode: 30, virtualKey: 65, modifiers: 0, unicodeChar: 97), // VK = A, UC = 'a'
+			new ScanCodeMapping (30, 65, ConsoleModifiers.Shift, 65),  // VK = A | Shift, UC = 'A'
+			new ScanCodeMapping (31, 83, 0, 115), // S
+			new ScanCodeMapping (31, 83, ConsoleModifiers.Shift, 83),
+			new ScanCodeMapping (32, 68, 0, 100), // D
+			new ScanCodeMapping (32, 68, ConsoleModifiers.Shift, 68),
+			new ScanCodeMapping (33, 70, 0, 102), // F
+			new ScanCodeMapping (33, 70, ConsoleModifiers.Shift, 70),
+			new ScanCodeMapping (34, 71, 0, 103), // G
+			new ScanCodeMapping (34, 71, ConsoleModifiers.Shift, 71),
+			new ScanCodeMapping (35, 72, 0, 104), // H
+			new ScanCodeMapping (35, 72, ConsoleModifiers.Shift, 72),
+			new ScanCodeMapping (36, 74, 0, 106), // J
+			new ScanCodeMapping (36, 74, ConsoleModifiers.Shift, 74),
+			new ScanCodeMapping (37, 75, 0, 107), // K
+			new ScanCodeMapping (37, 75, ConsoleModifiers.Shift, 75),
+			new ScanCodeMapping (38, 76, 0, 108), // L
+			new ScanCodeMapping (38, 76, ConsoleModifiers.Shift, 76),
+			new ScanCodeMapping (39, 192, 0, 231), // Oem3
+			new ScanCodeMapping (39, 192, ConsoleModifiers.Shift, 199),
+			new ScanCodeMapping (40, 222, 0, 186), // Oem7
+			new ScanCodeMapping (40, 222, ConsoleModifiers.Shift, 170),
+			new ScanCodeMapping (41, 220, 0, 92), // Oem5
+			new ScanCodeMapping (41, 220, ConsoleModifiers.Shift, 124),
+			new ScanCodeMapping (42, 16, 0, 0), // LShift
+			new ScanCodeMapping (42, 16, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (43, 191, 0, 126), // Oem2
+			new ScanCodeMapping (43, 191, ConsoleModifiers.Shift, 94),
+			new ScanCodeMapping (44, 90, 0, 122), // Z
+			new ScanCodeMapping (44, 90, ConsoleModifiers.Shift, 90),
+			new ScanCodeMapping (45, 88, 0, 120), // X
+			new ScanCodeMapping (45, 88, ConsoleModifiers.Shift, 88),
+			new ScanCodeMapping (46, 67, 0, 99), // C
+			new ScanCodeMapping (46, 67, ConsoleModifiers.Shift, 67),
+			new ScanCodeMapping (47, 86, 0, 118), // V
+			new ScanCodeMapping (47, 86, ConsoleModifiers.Shift, 86),
+			new ScanCodeMapping (48, 66, 0, 98), // B
+			new ScanCodeMapping (48, 66, ConsoleModifiers.Shift, 66),
+			new ScanCodeMapping (49, 78, 0, 110), // N
+			new ScanCodeMapping (49, 78, ConsoleModifiers.Shift, 78),
+			new ScanCodeMapping (50, 77, 0, 109), // M
+			new ScanCodeMapping (50, 77, ConsoleModifiers.Shift, 77),
+			new ScanCodeMapping (51, 188, 0, 44), // OemComma
+			new ScanCodeMapping (51, 188, ConsoleModifiers.Shift, 59),
+			new ScanCodeMapping (52, 190, 0, 46), // OemPeriod
+			new ScanCodeMapping (52, 190, ConsoleModifiers.Shift, 58),
+			new ScanCodeMapping (53, 189, 0, 45), // OemMinus
+			new ScanCodeMapping (53, 189, ConsoleModifiers.Shift, 95),
+			new ScanCodeMapping (54, 16, 0, 0), // RShift
+			new ScanCodeMapping (54, 16, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (55, 44, 0, 0), // PrintScreen
+			new ScanCodeMapping (55, 44, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (56, 18, 0, 0), // Alt
+			new ScanCodeMapping (56, 18, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (57, 32, 0, 32), // Spacebar
+			new ScanCodeMapping (57, 32, ConsoleModifiers.Shift, 32),
+			new ScanCodeMapping (58, 20, 0, 0), // Caps
+			new ScanCodeMapping (58, 20, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (59, 112, 0, 0), // F1
+			new ScanCodeMapping (59, 112, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (60, 113, 0, 0), // F2
+			new ScanCodeMapping (60, 113, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (61, 114, 0, 0), // F3
+			new ScanCodeMapping (61, 114, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (62, 115, 0, 0), // F4
+			new ScanCodeMapping (62, 115, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (63, 116, 0, 0), // F5
+			new ScanCodeMapping (63, 116, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (64, 117, 0, 0), // F6
+			new ScanCodeMapping (64, 117, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (65, 118, 0, 0), // F7
+			new ScanCodeMapping (65, 118, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (66, 119, 0, 0), // F8
+			new ScanCodeMapping (66, 119, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (67, 120, 0, 0), // F9
+			new ScanCodeMapping (67, 120, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (68, 121, 0, 0), // F10
+			new ScanCodeMapping (68, 121, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (69, 144, 0, 0), // Num
+			new ScanCodeMapping (69, 144, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (70, 145, 0, 0), // Scroll
+			new ScanCodeMapping (70, 145, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (71, 36, 0, 0), // Home
+			new ScanCodeMapping (71, 36, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (72, 38, 0, 0), // UpArrow
+			new ScanCodeMapping (72, 38, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (73, 33, 0, 0), // PageUp
+			new ScanCodeMapping (73, 33, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (74, 109, 0, 45), // Subtract
+			new ScanCodeMapping (74, 109, ConsoleModifiers.Shift, 45),
+			new ScanCodeMapping (75, 37, 0, 0), // LeftArrow
+			new ScanCodeMapping (75, 37, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (76, 12, 0, 0), // Center
+			new ScanCodeMapping (76, 12, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (77, 39, 0, 0), // RightArrow
+			new ScanCodeMapping (77, 39, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (78, 107, 0, 43), // Add
+			new ScanCodeMapping (78, 107, ConsoleModifiers.Shift, 43),
+			new ScanCodeMapping (79, 35, 0, 0), // End
+			new ScanCodeMapping (79, 35, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (80, 40, 0, 0), // DownArrow
+			new ScanCodeMapping (80, 40, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (81, 34, 0, 0), // PageDown
+			new ScanCodeMapping (81, 34, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (82, 45, 0, 0), // Insert
+			new ScanCodeMapping (82, 45, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (83, 46, 0, 0), // Delete
+			new ScanCodeMapping (83, 46, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (86, 226, 0, 60), // OEM 102
+			new ScanCodeMapping (86, 226, ConsoleModifiers.Shift, 62),
+			new ScanCodeMapping (87, 122, 0, 0), // F11
+			new ScanCodeMapping (87, 122, ConsoleModifiers.Shift, 0),
+			new ScanCodeMapping (88, 123, 0, 0), // F12
+			new ScanCodeMapping (88, 123, ConsoleModifiers.Shift, 0)
+		};
+
+		/// <summary>
+		/// Decode a <see cref="ConsoleKeyInfo"/> that is using <see cref="ConsoleKey.Packet"/>.
+		/// </summary>
+		/// <param name="consoleKeyInfo">The console key info.</param>
+		/// <returns>The decoded <see cref="ConsoleKeyInfo"/> or the <paramref name="consoleKeyInfo"/>.</returns>
+		/// <remarks>If it's a <see cref="ConsoleKey.Packet"/> the <see cref="ConsoleKeyInfo.KeyChar"/> may be
+		/// a <see cref="ConsoleKeyInfo.Key"/> or a <see cref="ConsoleKeyInfo.KeyChar"/> value.
+		/// </remarks>
+		public static ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
+		{
+			if (consoleKeyInfo.Key != ConsoleKey.Packet) {
+				return consoleKeyInfo;
+			}
+
+			return GetConsoleKeyFromKey (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out _);
+		}
+	}
+}

+ 132 - 183
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -6,6 +6,7 @@ using System.Collections.Generic;
 using System.Diagnostics;
 using System.Runtime.InteropServices;
 using System.Text;
+using Terminal.Gui.ConsoleDrivers;
 using Unix.Terminal;
 
 namespace Terminal.Gui;
@@ -343,103 +344,82 @@ internal class CursesDriver : ConsoleDriver {
 
 	public Curses.Window _window;
 
-	static Key MapCursesKey (int cursesKey)
+	static KeyCode MapCursesKey (int cursesKey)
 	{
 		switch (cursesKey) {
-		case Curses.KeyF1: return Key.F1;
-		case Curses.KeyF2: return Key.F2;
-		case Curses.KeyF3: return Key.F3;
-		case Curses.KeyF4: return Key.F4;
-		case Curses.KeyF5: return Key.F5;
-		case Curses.KeyF6: return Key.F6;
-		case Curses.KeyF7: return Key.F7;
-		case Curses.KeyF8: return Key.F8;
-		case Curses.KeyF9: return Key.F9;
-		case Curses.KeyF10: return Key.F10;
-		case Curses.KeyF11: return Key.F11;
-		case Curses.KeyF12: return Key.F12;
-		case Curses.KeyUp: return Key.CursorUp;
-		case Curses.KeyDown: return Key.CursorDown;
-		case Curses.KeyLeft: return Key.CursorLeft;
-		case Curses.KeyRight: return Key.CursorRight;
-		case Curses.KeyHome: return Key.Home;
-		case Curses.KeyEnd: return Key.End;
-		case Curses.KeyNPage: return Key.PageDown;
-		case Curses.KeyPPage: return Key.PageUp;
-		case Curses.KeyDeleteChar: return Key.DeleteChar;
-		case Curses.KeyInsertChar: return Key.InsertChar;
-		case Curses.KeyTab: return Key.Tab;
-		case Curses.KeyBackTab: return Key.BackTab;
-		case Curses.KeyBackspace: return Key.Backspace;
-		case Curses.ShiftKeyUp: return Key.CursorUp | Key.ShiftMask;
-		case Curses.ShiftKeyDown: return Key.CursorDown | Key.ShiftMask;
-		case Curses.ShiftKeyLeft: return Key.CursorLeft | Key.ShiftMask;
-		case Curses.ShiftKeyRight: return Key.CursorRight | Key.ShiftMask;
-		case Curses.ShiftKeyHome: return Key.Home | Key.ShiftMask;
-		case Curses.ShiftKeyEnd: return Key.End | Key.ShiftMask;
-		case Curses.ShiftKeyNPage: return Key.PageDown | Key.ShiftMask;
-		case Curses.ShiftKeyPPage: return Key.PageUp | Key.ShiftMask;
-		case Curses.AltKeyUp: return Key.CursorUp | Key.AltMask;
-		case Curses.AltKeyDown: return Key.CursorDown | Key.AltMask;
-		case Curses.AltKeyLeft: return Key.CursorLeft | Key.AltMask;
-		case Curses.AltKeyRight: return Key.CursorRight | Key.AltMask;
-		case Curses.AltKeyHome: return Key.Home | Key.AltMask;
-		case Curses.AltKeyEnd: return Key.End | Key.AltMask;
-		case Curses.AltKeyNPage: return Key.PageDown | Key.AltMask;
-		case Curses.AltKeyPPage: return Key.PageUp | Key.AltMask;
-		case Curses.CtrlKeyUp: return Key.CursorUp | Key.CtrlMask;
-		case Curses.CtrlKeyDown: return Key.CursorDown | Key.CtrlMask;
-		case Curses.CtrlKeyLeft: return Key.CursorLeft | Key.CtrlMask;
-		case Curses.CtrlKeyRight: return Key.CursorRight | Key.CtrlMask;
-		case Curses.CtrlKeyHome: return Key.Home | Key.CtrlMask;
-		case Curses.CtrlKeyEnd: return Key.End | Key.CtrlMask;
-		case Curses.CtrlKeyNPage: return Key.PageDown | Key.CtrlMask;
-		case Curses.CtrlKeyPPage: return Key.PageUp | Key.CtrlMask;
-		case Curses.ShiftCtrlKeyUp: return Key.CursorUp | Key.ShiftMask | Key.CtrlMask;
-		case Curses.ShiftCtrlKeyDown: return Key.CursorDown | Key.ShiftMask | Key.CtrlMask;
-		case Curses.ShiftCtrlKeyLeft: return Key.CursorLeft | Key.ShiftMask | Key.CtrlMask;
-		case Curses.ShiftCtrlKeyRight: return Key.CursorRight | Key.ShiftMask | Key.CtrlMask;
-		case Curses.ShiftCtrlKeyHome: return Key.Home | Key.ShiftMask | Key.CtrlMask;
-		case Curses.ShiftCtrlKeyEnd: return Key.End | Key.ShiftMask | Key.CtrlMask;
-		case Curses.ShiftCtrlKeyNPage: return Key.PageDown | Key.ShiftMask | Key.CtrlMask;
-		case Curses.ShiftCtrlKeyPPage: return Key.PageUp | Key.ShiftMask | Key.CtrlMask;
-		case Curses.ShiftAltKeyUp: return Key.CursorUp | Key.ShiftMask | Key.AltMask;
-		case Curses.ShiftAltKeyDown: return Key.CursorDown | Key.ShiftMask | Key.AltMask;
-		case Curses.ShiftAltKeyLeft: return Key.CursorLeft | Key.ShiftMask | Key.AltMask;
-		case Curses.ShiftAltKeyRight: return Key.CursorRight | Key.ShiftMask | Key.AltMask;
-		case Curses.ShiftAltKeyNPage: return Key.PageDown | Key.ShiftMask | Key.AltMask;
-		case Curses.ShiftAltKeyPPage: return Key.PageUp | Key.ShiftMask | Key.AltMask;
-		case Curses.ShiftAltKeyHome: return Key.Home | Key.ShiftMask | Key.AltMask;
-		case Curses.ShiftAltKeyEnd: return Key.End | Key.ShiftMask | Key.AltMask;
-		case Curses.AltCtrlKeyNPage: return Key.PageDown | Key.AltMask | Key.CtrlMask;
-		case Curses.AltCtrlKeyPPage: return Key.PageUp | Key.AltMask | Key.CtrlMask;
-		case Curses.AltCtrlKeyHome: return Key.Home | Key.AltMask | Key.CtrlMask;
-		case Curses.AltCtrlKeyEnd: return Key.End | Key.AltMask | Key.CtrlMask;
-		default: return Key.Unknown;
+		case Curses.KeyF1: return KeyCode.F1;
+		case Curses.KeyF2: return KeyCode.F2;
+		case Curses.KeyF3: return KeyCode.F3;
+		case Curses.KeyF4: return KeyCode.F4;
+		case Curses.KeyF5: return KeyCode.F5;
+		case Curses.KeyF6: return KeyCode.F6;
+		case Curses.KeyF7: return KeyCode.F7;
+		case Curses.KeyF8: return KeyCode.F8;
+		case Curses.KeyF9: return KeyCode.F9;
+		case Curses.KeyF10: return KeyCode.F10;
+		case Curses.KeyF11: return KeyCode.F11;
+		case Curses.KeyF12: return KeyCode.F12;
+		case Curses.KeyUp: return KeyCode.CursorUp;
+		case Curses.KeyDown: return KeyCode.CursorDown;
+		case Curses.KeyLeft: return KeyCode.CursorLeft;
+		case Curses.KeyRight: return KeyCode.CursorRight;
+		case Curses.KeyHome: return KeyCode.Home;
+		case Curses.KeyEnd: return KeyCode.End;
+		case Curses.KeyNPage: return KeyCode.PageDown;
+		case Curses.KeyPPage: return KeyCode.PageUp;
+		case Curses.KeyDeleteChar: return KeyCode.DeleteChar;
+		case Curses.KeyInsertChar: return KeyCode.InsertChar;
+		case Curses.KeyTab: return KeyCode.Tab;
+		case Curses.KeyBackTab: return KeyCode.Tab | KeyCode.ShiftMask;
+		case Curses.KeyBackspace: return KeyCode.Backspace;
+		case Curses.ShiftKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask;
+		case Curses.ShiftKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask;
+		case Curses.ShiftKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask;
+		case Curses.ShiftKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask;
+		case Curses.ShiftKeyHome: return KeyCode.Home | KeyCode.ShiftMask;
+		case Curses.ShiftKeyEnd: return KeyCode.End | KeyCode.ShiftMask;
+		case Curses.ShiftKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask;
+		case Curses.ShiftKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask;
+		case Curses.AltKeyUp: return KeyCode.CursorUp | KeyCode.AltMask;
+		case Curses.AltKeyDown: return KeyCode.CursorDown | KeyCode.AltMask;
+		case Curses.AltKeyLeft: return KeyCode.CursorLeft | KeyCode.AltMask;
+		case Curses.AltKeyRight: return KeyCode.CursorRight | KeyCode.AltMask;
+		case Curses.AltKeyHome: return KeyCode.Home | KeyCode.AltMask;
+		case Curses.AltKeyEnd: return KeyCode.End | KeyCode.AltMask;
+		case Curses.AltKeyNPage: return KeyCode.PageDown | KeyCode.AltMask;
+		case Curses.AltKeyPPage: return KeyCode.PageUp | KeyCode.AltMask;
+		case Curses.CtrlKeyUp: return KeyCode.CursorUp | KeyCode.CtrlMask;
+		case Curses.CtrlKeyDown: return KeyCode.CursorDown | KeyCode.CtrlMask;
+		case Curses.CtrlKeyLeft: return KeyCode.CursorLeft | KeyCode.CtrlMask;
+		case Curses.CtrlKeyRight: return KeyCode.CursorRight | KeyCode.CtrlMask;
+		case Curses.CtrlKeyHome: return KeyCode.Home | KeyCode.CtrlMask;
+		case Curses.CtrlKeyEnd: return KeyCode.End | KeyCode.CtrlMask;
+		case Curses.CtrlKeyNPage: return KeyCode.PageDown | KeyCode.CtrlMask;
+		case Curses.CtrlKeyPPage: return KeyCode.PageUp | KeyCode.CtrlMask;
+		case Curses.ShiftCtrlKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.CtrlMask;
+		case Curses.ShiftCtrlKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.CtrlMask;
+		case Curses.ShiftCtrlKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.CtrlMask;
+		case Curses.ShiftCtrlKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.CtrlMask;
+		case Curses.ShiftCtrlKeyHome: return KeyCode.Home | KeyCode.ShiftMask | KeyCode.CtrlMask;
+		case Curses.ShiftCtrlKeyEnd: return KeyCode.End | KeyCode.ShiftMask | KeyCode.CtrlMask;
+		case Curses.ShiftCtrlKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask | KeyCode.CtrlMask;
+		case Curses.ShiftCtrlKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.CtrlMask;
+		case Curses.ShiftAltKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.AltMask;
+		case Curses.ShiftAltKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.AltMask;
+		case Curses.ShiftAltKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.AltMask;
+		case Curses.ShiftAltKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.AltMask;
+		case Curses.ShiftAltKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask | KeyCode.AltMask;
+		case Curses.ShiftAltKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.AltMask;
+		case Curses.ShiftAltKeyHome: return KeyCode.Home | KeyCode.ShiftMask | KeyCode.AltMask;
+		case Curses.ShiftAltKeyEnd: return KeyCode.End | KeyCode.ShiftMask | KeyCode.AltMask;
+		case Curses.AltCtrlKeyNPage: return KeyCode.PageDown | KeyCode.AltMask | KeyCode.CtrlMask;
+		case Curses.AltCtrlKeyPPage: return KeyCode.PageUp | KeyCode.AltMask | KeyCode.CtrlMask;
+		case Curses.AltCtrlKeyHome: return KeyCode.Home | KeyCode.AltMask | KeyCode.CtrlMask;
+		case Curses.AltCtrlKeyEnd: return KeyCode.End | KeyCode.AltMask | KeyCode.CtrlMask;
+		default: return KeyCode.Unknown;
 		}
 	}
 
-	KeyModifiers _keyModifiers;
-
-	KeyModifiers MapKeyModifiers (Key key)
-	{
-		if (_keyModifiers == null) {
-			_keyModifiers = new KeyModifiers ();
-		}
-
-		if (!_keyModifiers.Shift && (key & Key.ShiftMask) != 0) {
-			_keyModifiers.Shift = true;
-		}
-		if (!_keyModifiers.Alt && (key & Key.AltMask) != 0) {
-			_keyModifiers.Alt = true;
-		}
-		if (!_keyModifiers.Ctrl && (key & Key.CtrlMask) != 0) {
-			_keyModifiers.Ctrl = true;
-		}
-
-		return _keyModifiers;
-	}
-
 	internal void ProcessInput ()
 	{
 		int wch;
@@ -448,9 +428,7 @@ internal class CursesDriver : ConsoleDriver {
 		if (code == Curses.ERR) {
 			return;
 		}
-
-		_keyModifiers = new KeyModifiers ();
-		Key k = Key.Null;
+		KeyCode k = KeyCode.Null;
 
 		if (code == Curses.KEY_CODE_YES) {
 			while (code == Curses.KEY_CODE_YES && wch == Curses.KeyResize) {
@@ -464,14 +442,14 @@ internal class CursesDriver : ConsoleDriver {
 				int wch2 = wch;
 
 				while (wch2 == Curses.KeyMouse) {
-					KeyEvent key = null;
+					Key kea = null;
 					ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] {
-							new ConsoleKeyInfo ((char)Key.Esc, 0, false, false, false),
+							new ConsoleKeyInfo ((char)KeyCode.Esc, 0, false, false, false),
 							new ConsoleKeyInfo ('[', 0, false, false, false),
 							new ConsoleKeyInfo ('<', 0, false, false, false)
 						};
 					code = 0;
-					HandleEscSeqResponse (ref code, ref k, ref wch2, ref key, ref cki);
+					HandleEscSeqResponse (ref code, ref k, ref wch2, ref kea, ref cki);
 				}
 				return;
 			}
@@ -479,27 +457,26 @@ internal class CursesDriver : ConsoleDriver {
 			if (wch >= 277 && wch <= 288) {
 				// Shift+(F1 - F12)
 				wch -= 12;
-				k = Key.ShiftMask | MapCursesKey (wch);
+				k = KeyCode.ShiftMask | MapCursesKey (wch);
 			} else if (wch >= 289 && wch <= 300) {
 				// Ctrl+(F1 - F12)
 				wch -= 24;
-				k = Key.CtrlMask | MapCursesKey (wch);
+				k = KeyCode.CtrlMask | MapCursesKey (wch);
 			} else if (wch >= 301 && wch <= 312) {
 				// Ctrl+Shift+(F1 - F12)
 				wch -= 36;
-				k = Key.CtrlMask | Key.ShiftMask | MapCursesKey (wch);
+				k = KeyCode.CtrlMask | KeyCode.ShiftMask | MapCursesKey (wch);
 			} else if (wch >= 313 && wch <= 324) {
 				// Alt+(F1 - F12)
 				wch -= 48;
-				k = Key.AltMask | MapCursesKey (wch);
+				k = KeyCode.AltMask | MapCursesKey (wch);
 			} else if (wch >= 325 && wch <= 327) {
 				// Shift+Alt+(F1 - F3)
 				wch -= 60;
-				k = Key.ShiftMask | Key.AltMask | MapCursesKey (wch);
+				k = KeyCode.ShiftMask | KeyCode.AltMask | MapCursesKey (wch);
 			}
-			OnKeyDown (new KeyEventEventArgs (new KeyEvent (k, MapKeyModifiers (k))));
-			OnKeyUp (new KeyEventEventArgs (new KeyEvent (k, MapKeyModifiers (k))));
-			OnKeyPressed (new KeyEventEventArgs (new KeyEvent (k, MapKeyModifiers (k))));
+			OnKeyDown (new Key (k));
+			OnKeyUp (new Key (k));
 			return;
 		}
 
@@ -510,85 +487,73 @@ internal class CursesDriver : ConsoleDriver {
 			code = Curses.get_wch (out int wch2);
 
 			if (code == Curses.KEY_CODE_YES) {
-				k = Key.AltMask | MapCursesKey (wch);
+				k = KeyCode.AltMask | MapCursesKey (wch);
 			}
+			Key key = null;
 			if (code == 0) {
-				KeyEvent key = null;
 
 				// The ESC-number handling, debatable.
 				// Simulates the AltMask itself by pressing Alt + Space.
-				if (wch2 == (int)Key.Space) {
-					k = Key.AltMask;
-				} else if (wch2 - (int)Key.Space >= (uint)Key.A && wch2 - (int)Key.Space <= (uint)Key.Z) {
-					k = (Key)((uint)Key.AltMask + (wch2 - (int)Key.Space));
-				} else if (wch2 >= (uint)Key.A - 64 && wch2 <= (uint)Key.Z - 64) {
-					k = (Key)((uint)(Key.AltMask | Key.CtrlMask) + (wch2 + 64));
-				} else if (wch2 >= (uint)Key.D0 && wch2 <= (uint)Key.D9) {
-					k = (Key)((uint)Key.AltMask + (uint)Key.D0 + (wch2 - (uint)Key.D0));
+				if (wch2 == (int)KeyCode.Space) {
+					k = KeyCode.AltMask;
+				} else if (wch2 - (int)KeyCode.Space >= (uint)KeyCode.A && wch2 - (int)KeyCode.Space <= (uint)KeyCode.Z) {
+					k = (KeyCode)((uint)KeyCode.AltMask + (wch2 - (int)KeyCode.Space));
+				} else if (wch2 >= (uint)KeyCode.A - 64 && wch2 <= (uint)KeyCode.Z - 64) {
+					k = (KeyCode)((uint)(KeyCode.AltMask | KeyCode.CtrlMask) + (wch2 + 64));
+				} else if (wch2 >= (uint)KeyCode.D0 && wch2 <= (uint)KeyCode.D9) {
+					k = (KeyCode)((uint)KeyCode.AltMask + (uint)KeyCode.D0 + (wch2 - (uint)KeyCode.D0));
 				} else if (wch2 == Curses.KeyCSI) {
 					ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] {
-							new ConsoleKeyInfo ((char)Key.Esc, 0, false, false, false),
+							new ConsoleKeyInfo ((char)KeyCode.Esc, 0, false, false, false),
 							new ConsoleKeyInfo ('[', 0, false, false, false)
 						};
 					HandleEscSeqResponse (ref code, ref k, ref wch2, ref key, ref cki);
 					return;
 				} else {
 					// Unfortunately there are no way to differentiate Ctrl+Alt+alfa and Ctrl+Shift+Alt+alfa.
-					if (((Key)wch2 & Key.CtrlMask) != 0) {
-						_keyModifiers.Ctrl = true;
+					if (((KeyCode)wch2 & KeyCode.CtrlMask) != 0) {
+						k = (KeyCode)((uint)KeyCode.CtrlMask + (wch2 & ~((int)KeyCode.CtrlMask)));
 					}
 					if (wch2 == 0) {
-						k = Key.CtrlMask | Key.AltMask | Key.Space;
-					} else if (wch >= (uint)Key.A && wch <= (uint)Key.Z) {
-						_keyModifiers.Shift = true;
-						_keyModifiers.Alt = true;
+						k = KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Space;
+					} else if (wch >= (uint)KeyCode.A && wch <= (uint)KeyCode.Z) {
+						k = KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.Space;
 					} else if (wch2 < 256) {
-						k = (Key)wch2;
-						_keyModifiers.Alt = true;
+						k = (KeyCode)wch2 | KeyCode.AltMask;
 					} else {
-						k = (Key)((uint)(Key.AltMask | Key.CtrlMask) + wch2);
+						k = (KeyCode)((uint)(KeyCode.AltMask | KeyCode.CtrlMask) + wch2);
 					}
 				}
-				key = new KeyEvent (k, MapKeyModifiers (k));
-				OnKeyDown (new KeyEventEventArgs (key));
-				OnKeyUp (new KeyEventEventArgs (key));
-				OnKeyPressed (new KeyEventEventArgs (key));
+				key = new Key (k);
 			} else {
-				k = Key.Esc;
-				OnKeyPressed (new KeyEventEventArgs (new KeyEvent (k, MapKeyModifiers (k))));
+				key = new Key (KeyCode.Esc);
 			}
+			OnKeyDown (key);
+			OnKeyUp (key);
 		} else if (wch == Curses.KeyTab) {
 			k = MapCursesKey (wch);
-			OnKeyDown (new KeyEventEventArgs (new KeyEvent (k, MapKeyModifiers (k))));
-			OnKeyUp (new KeyEventEventArgs (new KeyEvent (k, MapKeyModifiers (k))));
-			OnKeyPressed (new KeyEventEventArgs (new KeyEvent (k, MapKeyModifiers (k))));
+			OnKeyDown (new Key (k));
+			OnKeyUp (new Key (k));
 		} else {
 			// Unfortunately there are no way to differentiate Ctrl+alfa and Ctrl+Shift+alfa.
-			k = (Key)wch;
+			k = (KeyCode)wch;
 			if (wch == 0) {
-				k = Key.CtrlMask | Key.Space;
-			} else if (wch >= (uint)Key.A - 64 && wch <= (uint)Key.Z - 64) {
-				if ((Key)(wch + 64) != Key.J) {
-					k = Key.CtrlMask | (Key)(wch + 64);
+				k = KeyCode.CtrlMask | KeyCode.Space;
+			} else if (wch >= (uint)KeyCode.A - 64 && wch <= (uint)KeyCode.Z - 64) {
+				if ((KeyCode)(wch + 64) != KeyCode.J) {
+					k = KeyCode.CtrlMask | (KeyCode)(wch + 64);
 				}
-			} else if (wch >= (uint)Key.A && wch <= (uint)Key.Z) {
-				_keyModifiers.Shift = true;
-			}
-			OnKeyDown (new KeyEventEventArgs (new KeyEvent (k, MapKeyModifiers (k))));
-			OnKeyUp (new KeyEventEventArgs (new KeyEvent (k, MapKeyModifiers (k))));
-			OnKeyPressed (new KeyEventEventArgs (new KeyEvent (k, MapKeyModifiers (k))));
-		}
-		// Cause OnKeyUp and OnKeyPressed. Note that the special handling for ESC above 
-		// will not impact KeyUp.
-		// This is causing ESC firing even if another keystroke was handled.
-		//if (wch == Curses.KeyTab) {
-		//	keyUpHandler (new KeyEvent (MapCursesKey (wch), keyModifiers));
-		//} else {
-		//	keyUpHandler (new KeyEvent ((Key)wch, keyModifiers));
-		//}
+			} else if (wch >= (uint)KeyCode.A && wch <= (uint)KeyCode.Z) {
+				k = (KeyCode)wch | KeyCode.ShiftMask;
+			} else if (wch <= 'z') {
+				k = (KeyCode)wch & ~KeyCode.Space;
+			} 
+			OnKeyDown (new Key (k));
+			OnKeyUp (new Key (k));
+		}
 	}
 
-	void HandleEscSeqResponse (ref int code, ref Key k, ref int wch2, ref KeyEvent key, ref ConsoleKeyInfo [] cki)
+	void HandleEscSeqResponse (ref int code, ref KeyCode k, ref int wch2, ref Key keyEventArgs, ref ConsoleKeyInfo [] cki)
 	{
 		ConsoleKey ck = 0;
 		ConsoleModifiers mod = 0;
@@ -603,15 +568,14 @@ internal class CursesDriver : ConsoleDriver {
 					}
 					cki = null;
 					if (wch2 == 27) {
-						cki = EscSeqUtils.ResizeArray (new ConsoleKeyInfo ((char)Key.Esc, 0,
+						cki = EscSeqUtils.ResizeArray (new ConsoleKeyInfo ((char)KeyCode.Esc, 0,
 							false, false, false), cki);
 					}
 				} else {
 					k = ConsoleKeyMapping.MapConsoleKeyToKey (consoleKeyInfo.Key, out _);
 					k = ConsoleKeyMapping.MapKeyModifiers (consoleKeyInfo, k);
-					key = new KeyEvent (k, MapKeyModifiers (k));
-					OnKeyDown (new KeyEventEventArgs (key));
-					OnKeyPressed (new KeyEventEventArgs (key));
+					keyEventArgs = new (k);
+					OnKeyDown (keyEventArgs);
 				}
 			} else {
 				cki = EscSeqUtils.ResizeArray (consoleKeyInfo, cki);
@@ -747,7 +711,7 @@ internal class CursesDriver : ConsoleDriver {
 
 	public override void SendKeys (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control)
 	{
-		Key key;
+		KeyCode key;
 
 		if (consoleKey == ConsoleKey.Packet) {
 			ConsoleModifiers mod = new ConsoleModifiers ();
@@ -760,33 +724,18 @@ internal class CursesDriver : ConsoleDriver {
 			if (control) {
 				mod |= ConsoleModifiers.Control;
 			}
-			var kchar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (keyChar, mod, out uint ckey, out _);
-			key = ConsoleKeyMapping.MapConsoleKeyToKey ((ConsoleKey)ckey, out bool mappable);
+			var cKeyInfo = ConsoleKeyMapping.GetConsoleKeyFromKey (keyChar, mod, out _);
+			key = ConsoleKeyMapping.MapConsoleKeyToKey ((ConsoleKey)cKeyInfo.Key, out bool mappable);
 			if (mappable) {
-				key = (Key)kchar;
+				key = (KeyCode)cKeyInfo.KeyChar;
 			}
 		} else {
-			key = (Key)keyChar;
+			key = (KeyCode)keyChar;
 		}
 
-		KeyModifiers km = new KeyModifiers ();
-		if (shift) {
-			if (keyChar == 0) {
-				key |= Key.ShiftMask;
-			}
-			km.Shift = shift;
-		}
-		if (alt) {
-			key |= Key.AltMask;
-			km.Alt = alt;
-		}
-		if (control) {
-			key |= Key.CtrlMask;
-			km.Ctrl = control;
-		}
-		OnKeyDown (new KeyEventEventArgs (new KeyEvent (key, km)));
-		OnKeyPressed (new KeyEventEventArgs (new KeyEvent (key, km)));
-		OnKeyUp (new KeyEventEventArgs (new KeyEvent (key, km)));
+		OnKeyDown (new Key (key));
+		OnKeyUp (new Key (key));
+		//OnKeyPressed (new KeyEventArgsEventArgs (key));
 	}
 
 

+ 1 - 1
Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs

@@ -21,7 +21,7 @@ public static class EscSeqUtils {
 	/// <summary>
 	/// Escape key code (ASCII 27/0x1B).
 	/// </summary>
-	public static readonly char KeyEsc = (char)Key.Esc;
+	public static readonly char KeyEsc = (char)KeyCode.Esc;
 
 	/// <summary>
 	/// ESC [ - The CSI (Control Sequence Introducer).

+ 8 - 29
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs

@@ -5,6 +5,7 @@ using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Text;
+using Terminal.Gui.ConsoleDrivers;
 using Rune = System.Text.Rune;
 
 namespace Terminal.Gui;
@@ -461,28 +462,6 @@ public static class FakeConsole {
 	public static bool KeyAvailable { get; }
 	//
 	// Summary:
-	//	Gets a value indicating whether the NUM LOCK keyboard toggle is turned on or
-	//	turned off.
-	//
-	// Returns:
-	//	true if NUM LOCK is turned on; false if NUM LOCK is turned off.
-	/// <summary>
-	/// 
-	/// </summary>
-	public static bool NumberLock { get; }
-	//
-	// Summary:
-	//	Gets a value indicating whether the CAPS LOCK keyboard toggle is turned on or
-	//	turned off.
-	//
-	// Returns:
-	//	true if CAPS LOCK is turned on; false if CAPS LOCK is turned off.
-	/// <summary>
-	/// 
-	/// </summary>
-	public static bool CapsLock { get; }
-	//
-	// Summary:
 	//	Gets a value that indicates whether input has been redirected from the standard
 	//	input stream.
 	//
@@ -814,17 +793,17 @@ public static class FakeConsole {
 	public static Stack<ConsoleKeyInfo> MockKeyPresses = new Stack<ConsoleKeyInfo> ();
 
 	/// <summary>
-	///  Helper to push a <see cref="Key"/> onto <see cref="MockKeyPresses"/>.
+	///  Helper to push a <see cref="KeyCode"/> onto <see cref="MockKeyPresses"/>.
 	/// </summary>
 	/// <param name="key"></param>
-	public static void PushMockKeyPress (Key key)
+	public static void PushMockKeyPress (KeyCode key)
 	{
 		MockKeyPresses.Push (new ConsoleKeyInfo (
-			(char)(key & ~Key.CtrlMask & ~Key.ShiftMask & ~Key.AltMask),
-			ConsoleKeyMapping.GetConsoleKeyFromKey (key),
-			key.HasFlag (Key.ShiftMask),
-			key.HasFlag (Key.AltMask),
-			key.HasFlag (Key.CtrlMask)));
+			(char)(key & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask & ~KeyCode.AltMask),
+			ConsoleKeyMapping.GetConsoleKeyFromKey (key).Key,
+			key.HasFlag (KeyCode.ShiftMask),
+			key.HasFlag (KeyCode.AltMask),
+			key.HasFlag (KeyCode.CtrlMask)));
 	}
 
 	//

+ 46 - 133
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -5,11 +5,13 @@ using System;
 using System.Diagnostics;
 using System.Runtime.InteropServices;
 using System.Text;
+using Terminal.Gui.ConsoleDrivers;
 
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
 namespace Terminal.Gui;
+
 /// <summary>
 /// Implements a mock ConsoleDriver for unit testing
 /// </summary>
@@ -17,9 +19,10 @@ public class FakeDriver : ConsoleDriver {
 #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
 
 	public class Behaviors {
-
 		public bool UseFakeClipboard { get; internal set; }
+
 		public bool FakeClipboardAlwaysThrowsNotSupportedException { get; internal set; }
+
 		public bool FakeClipboardIsSupportedAlwaysFalse { get; internal set; }
 
 		public Behaviors (bool useFakeClipboard = false, bool fakeClipboardAlwaysThrowsNotSupportedException = false, bool fakeClipboardIsSupportedAlwaysTrue = false)
@@ -75,9 +78,9 @@ public class FakeDriver : ConsoleDriver {
 		ResizeScreen ();
 		CurrentAttribute = new Attribute (Color.White, Color.Black);
 		ClearContents ();
-		
+
 		_mainLoopDriver = new FakeMainLoop (this);
-		_mainLoopDriver.KeyPressed = ProcessInput;
+		_mainLoopDriver.MockKeyPressed = MockKeyPressedHandler;
 		return new MainLoop (_mainLoopDriver);
 	}
 
@@ -181,7 +184,6 @@ public class FakeDriver : ConsoleDriver {
 	}
 
 	#region Color Handling
-
 	///// <remarks>
 	///// In the FakeDriver, colors are encoded as an int; same as NetDriver
 	///// However, the foreground color is stored in the most significant 16 bits, 
@@ -196,62 +198,46 @@ public class FakeDriver : ConsoleDriver {
 	//		background: background
 	//	);
 	//}
-
 	#endregion
 
-	public ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
-	{
-		if (consoleKeyInfo.Key != ConsoleKey.Packet) {
-			return consoleKeyInfo;
-		}
-
-		var mod = consoleKeyInfo.Modifiers;
-		var shift = (mod & ConsoleModifiers.Shift) != 0;
-		var alt = (mod & ConsoleModifiers.Alt) != 0;
-		var control = (mod & ConsoleModifiers.Control) != 0;
-
-		var keyChar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out uint virtualKey, out _);
 
-		return new ConsoleKeyInfo ((char)keyChar, (ConsoleKey)virtualKey, shift, alt, control);
-	}
-
-	Key MapKey (ConsoleKeyInfo keyInfo)
+	KeyCode MapKey (ConsoleKeyInfo keyInfo)
 	{
 		switch (keyInfo.Key) {
 		case ConsoleKey.Escape:
-			return MapKeyModifiers (keyInfo, Key.Esc);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Esc);
 		case ConsoleKey.Tab:
-			return keyInfo.Modifiers == ConsoleModifiers.Shift ? Key.BackTab : Key.Tab;
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Tab);
 		case ConsoleKey.Clear:
-			return MapKeyModifiers (keyInfo, Key.Clear);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Clear);
 		case ConsoleKey.Home:
-			return MapKeyModifiers (keyInfo, Key.Home);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Home);
 		case ConsoleKey.End:
-			return MapKeyModifiers (keyInfo, Key.End);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.End);
 		case ConsoleKey.LeftArrow:
-			return MapKeyModifiers (keyInfo, Key.CursorLeft);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorLeft);
 		case ConsoleKey.RightArrow:
-			return MapKeyModifiers (keyInfo, Key.CursorRight);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorRight);
 		case ConsoleKey.UpArrow:
-			return MapKeyModifiers (keyInfo, Key.CursorUp);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorUp);
 		case ConsoleKey.DownArrow:
-			return MapKeyModifiers (keyInfo, Key.CursorDown);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorDown);
 		case ConsoleKey.PageUp:
-			return MapKeyModifiers (keyInfo, Key.PageUp);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageUp);
 		case ConsoleKey.PageDown:
-			return MapKeyModifiers (keyInfo, Key.PageDown);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageDown);
 		case ConsoleKey.Enter:
-			return MapKeyModifiers (keyInfo, Key.Enter);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Enter);
 		case ConsoleKey.Spacebar:
-			return MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? Key.Space : (Key)keyInfo.KeyChar);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? KeyCode.Space : (KeyCode)keyInfo.KeyChar);
 		case ConsoleKey.Backspace:
-			return MapKeyModifiers (keyInfo, Key.Backspace);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Backspace);
 		case ConsoleKey.Delete:
-			return MapKeyModifiers (keyInfo, Key.DeleteChar);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.DeleteChar);
 		case ConsoleKey.Insert:
-			return MapKeyModifiers (keyInfo, Key.InsertChar);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.InsertChar);
 		case ConsoleKey.PrintScreen:
-			return MapKeyModifiers (keyInfo, Key.PrintScreen);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PrintScreen);
 
 		case ConsoleKey.Oem1:
 		case ConsoleKey.Oem2:
@@ -267,114 +253,42 @@ public class FakeDriver : ConsoleDriver {
 		case ConsoleKey.OemPlus:
 		case ConsoleKey.OemMinus:
 			if (keyInfo.KeyChar == 0) {
-				return Key.Unknown;
+				return KeyCode.Unknown;
 			}
 
-			return (Key)((uint)keyInfo.KeyChar);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)keyInfo.KeyChar));
 		}
 
 		var key = keyInfo.Key;
 		if (key >= ConsoleKey.A && key <= ConsoleKey.Z) {
 			var delta = key - ConsoleKey.A;
-			if (keyInfo.Modifiers == ConsoleModifiers.Control) {
-				return (Key)(((uint)Key.CtrlMask) | ((uint)Key.A + delta));
-			}
-			if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
-				return (Key)(((uint)Key.AltMask) | ((uint)Key.A + delta));
+			if (keyInfo.KeyChar != (uint)key) {
+				return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)keyInfo.KeyChar);
 			}
-			if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) {
-				return MapKeyModifiers (keyInfo, (Key)((uint)Key.A + delta));
-			}
-			if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-				if (keyInfo.KeyChar == 0) {
-					return (Key)(((uint)Key.AltMask | (uint)Key.CtrlMask) | ((uint)Key.A + delta));
-				} else {
-					return (Key)((uint)keyInfo.KeyChar);
-				}
+			if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)
+			|| keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt)
+			|| keyInfo.Modifiers.HasFlag (ConsoleModifiers.Shift)) {
+				return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.A + delta));
 			}
-			return (Key)((uint)keyInfo.KeyChar);
-		}
-		if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9) {
-			var delta = key - ConsoleKey.D0;
-			if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
-				return (Key)(((uint)Key.AltMask) | ((uint)Key.D0 + delta));
-			}
-			if (keyInfo.Modifiers == ConsoleModifiers.Control) {
-				return (Key)(((uint)Key.CtrlMask) | ((uint)Key.D0 + delta));
-			}
-			if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) {
-				return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta));
-			}
-			if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-				if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30) {
-					return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta));
-				}
-			}
-			return (Key)((uint)keyInfo.KeyChar);
-		}
-		if (key >= ConsoleKey.F1 && key <= ConsoleKey.F12) {
-			var delta = key - ConsoleKey.F1;
-			if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-				return MapKeyModifiers (keyInfo, (Key)((uint)Key.F1 + delta));
-			}
-
-			return (Key)((uint)Key.F1 + delta);
-		}
-		if (keyInfo.KeyChar != 0) {
-			return MapKeyModifiers (keyInfo, (Key)((uint)keyInfo.KeyChar));
+			var alphaBase = ((keyInfo.Modifiers != ConsoleModifiers.Shift)) ? 'A' : 'a';
+			return (KeyCode)((uint)alphaBase + delta);
 		}
 
-		return (Key)(0xffffffff);
-	}
-
-	KeyModifiers keyModifiers;
-
-	private Key MapKeyModifiers (ConsoleKeyInfo keyInfo, Key key)
-	{
-		Key keyMod = new Key ();
-		if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0) {
-			keyMod = Key.ShiftMask;
-		}
-		if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0) {
-			keyMod |= Key.CtrlMask;
-		}
-		if ((keyInfo.Modifiers & ConsoleModifiers.Alt) != 0) {
-			keyMod |= Key.AltMask;
-		}
-
-		return keyMod != Key.Null ? keyMod | key : key;
+		return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)keyInfo.KeyChar));
 	}
 
 	private CursorVisibility _savedCursorVisibility;
 
-
-	void ProcessInput (ConsoleKeyInfo consoleKey)
+	void MockKeyPressedHandler (ConsoleKeyInfo consoleKeyInfo)
 	{
-		if (consoleKey.Key == ConsoleKey.Packet) {
-			consoleKey = FromVKPacketToKConsoleKeyInfo (consoleKey);
-		}
-		keyModifiers = new KeyModifiers ();
-		if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Shift)) {
-			keyModifiers.Shift = true;
-		}
-		if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Alt)) {
-			keyModifiers.Alt = true;
-		}
-		if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Control)) {
-			keyModifiers.Ctrl = true;
-		}
-		var map = MapKey (consoleKey);
-		if (map == (Key)0xffffffff) {
-			if ((consoleKey.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-				OnKeyDown(new KeyEventEventArgs(new KeyEvent (map, keyModifiers)));
-				OnKeyUp (new KeyEventEventArgs (new KeyEvent (map, keyModifiers)));
-			}
-			return;
+		if (consoleKeyInfo.Key == ConsoleKey.Packet) {
+			consoleKeyInfo = ConsoleKeyMapping.FromVKPacketToKConsoleKeyInfo (consoleKeyInfo);
 		}
 
-		OnKeyDown (new KeyEventEventArgs (new KeyEvent (map, keyModifiers)));
-		OnKeyUp (new KeyEventEventArgs (new KeyEvent (map, keyModifiers)));
-		OnKeyPressed (new KeyEventEventArgs (new KeyEvent (map, keyModifiers)));
+		var map = MapKey (consoleKeyInfo);
+		OnKeyDown (new Key (map));
+		OnKeyUp (new Key (map));
+		//OnKeyPressed (new KeyEventArgs (map));
 	}
 
 	/// <inheritdoc/>
@@ -410,7 +324,7 @@ public class FakeDriver : ConsoleDriver {
 
 	public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)
 	{
-		ProcessInput (new ConsoleKeyInfo (keyChar, key, shift, alt, control));
+		MockKeyPressedHandler (new ConsoleKeyInfo (keyChar, key, shift, alt, control));
 	}
 
 	public void SetBufferSize (int width, int height)
@@ -480,15 +394,14 @@ public class FakeDriver : ConsoleDriver {
 			if (Col >= 0 && Col < FakeConsole.BufferWidth && Row >= 0 && Row < FakeConsole.BufferHeight) {
 				FakeConsole.SetCursorPosition (Col, Row);
 			}
-		} catch (System.IO.IOException) {
-		} catch (ArgumentOutOfRangeException) {
-		}
+		} catch (System.IO.IOException) { } catch (ArgumentOutOfRangeException) { }
 	}
 
 	#region Not Implemented
 	public override void Suspend ()
 	{
-		throw new NotImplementedException ();
+		return;
+		//throw new NotImplementedException ();
 	}
 	#endregion
 

+ 2 - 2
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs

@@ -5,7 +5,7 @@ namespace Terminal.Gui;
 
 internal class FakeMainLoop : IMainLoopDriver {
 
-	public Action<ConsoleKeyInfo> KeyPressed;
+	public Action<ConsoleKeyInfo> MockKeyPressed;
 
 	public FakeMainLoop (ConsoleDriver consoleDriver = null)
 	{
@@ -31,7 +31,7 @@ internal class FakeMainLoop : IMainLoopDriver {
 	public void Iteration ()
 	{
 		if (FakeConsole.MockKeyPresses.Count > 0) {
-			KeyPressed?.Invoke (FakeConsole.MockKeyPresses.Pop ());
+			MockKeyPressed?.Invoke (FakeConsole.MockKeyPresses.Pop ());
 		}
 	}
 

+ 71 - 74
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -10,6 +10,7 @@ using System.Runtime.InteropServices;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Text;
+using Terminal.Gui.ConsoleDrivers;
 using static Terminal.Gui.NetEvents;
 
 namespace Terminal.Gui;
@@ -208,11 +209,11 @@ internal class NetEvents : IDisposable {
 					} catch (OperationCanceledException) {
 						return;
 					}
-					if ((consoleKeyInfo.KeyChar == (char)Key.Esc && !_isEscSeq)
-						|| (consoleKeyInfo.KeyChar != (char)Key.Esc && _isEscSeq)) {
+					if ((consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq)
+						|| (consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq)) {
 
-						if (_cki == null && consoleKeyInfo.KeyChar != (char)Key.Esc && _isEscSeq) {
-							_cki = EscSeqUtils.ResizeArray (new ConsoleKeyInfo ((char)Key.Esc, 0,
+						if (_cki == null && consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq) {
+							_cki = EscSeqUtils.ResizeArray (new ConsoleKeyInfo ((char)KeyCode.Esc, 0,
 							    false, false, false), _cki);
 						}
 						_isEscSeq = true;
@@ -223,7 +224,7 @@ internal class NetEvents : IDisposable {
 						_cki = null;
 						_isEscSeq = false;
 						break;
-					} else if (consoleKeyInfo.KeyChar == (char)Key.Esc && _isEscSeq && _cki != null) {
+					} else if (consoleKeyInfo.KeyChar == (char)KeyCode.Esc && _isEscSeq && _cki != null) {
 						ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
 						_cki = null;
 						if (Console.KeyAvailable) {
@@ -822,7 +823,7 @@ internal class NetDriver : ConsoleDriver {
 						//	output.Append (combMark);
 						//}
 						// WriteToConsole (output, ref lastCol, row, ref outputWidth);
-					} else  if ((rune.IsSurrogatePair () && rune.GetColumns () < 2)) {
+					} else if ((rune.IsSurrogatePair () && rune.GetColumns () < 2)) {
 						WriteToConsole (output, ref lastCol, row, ref outputWidth);
 						SetCursorPosition (col - 1, row);
 					}
@@ -997,45 +998,44 @@ internal class NetDriver : ConsoleDriver {
 		var alt = (mod & ConsoleModifiers.Alt) != 0;
 		var control = (mod & ConsoleModifiers.Control) != 0;
 
-		var keyChar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out uint virtualKey, out _);
+		var cKeyInfo = ConsoleKeyMapping.GetConsoleKeyFromKey (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out _);
 
-		return new ConsoleKeyInfo ((char)keyChar, (ConsoleKey)virtualKey, shift, alt, control);
+		return new ConsoleKeyInfo (cKeyInfo.KeyChar, cKeyInfo.Key, shift, alt, control);
 	}
 
-	Key MapKey (ConsoleKeyInfo keyInfo)
+	KeyCode MapKey (ConsoleKeyInfo keyInfo)
 	{
-		MapKeyModifiers (keyInfo, (Key)keyInfo.Key);
 		switch (keyInfo.Key) {
 		case ConsoleKey.Escape:
-			return MapKeyModifiers (keyInfo, Key.Esc);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Esc);
 		case ConsoleKey.Tab:
-			return keyInfo.Modifiers == ConsoleModifiers.Shift ? Key.BackTab : Key.Tab;
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Tab);
 		case ConsoleKey.Home:
-			return MapKeyModifiers (keyInfo, Key.Home);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Home);
 		case ConsoleKey.End:
-			return MapKeyModifiers (keyInfo, Key.End);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.End);
 		case ConsoleKey.LeftArrow:
-			return MapKeyModifiers (keyInfo, Key.CursorLeft);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorLeft);
 		case ConsoleKey.RightArrow:
-			return MapKeyModifiers (keyInfo, Key.CursorRight);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorRight);
 		case ConsoleKey.UpArrow:
-			return MapKeyModifiers (keyInfo, Key.CursorUp);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorUp);
 		case ConsoleKey.DownArrow:
-			return MapKeyModifiers (keyInfo, Key.CursorDown);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorDown);
 		case ConsoleKey.PageUp:
-			return MapKeyModifiers (keyInfo, Key.PageUp);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageUp);
 		case ConsoleKey.PageDown:
-			return MapKeyModifiers (keyInfo, Key.PageDown);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageDown);
 		case ConsoleKey.Enter:
-			return MapKeyModifiers (keyInfo, Key.Enter);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Enter);
 		case ConsoleKey.Spacebar:
-			return MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? Key.Space : (Key)keyInfo.KeyChar);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? KeyCode.Space : (KeyCode)keyInfo.KeyChar);
 		case ConsoleKey.Backspace:
-			return MapKeyModifiers (keyInfo, Key.Backspace);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Backspace);
 		case ConsoleKey.Delete:
-			return MapKeyModifiers (keyInfo, Key.DeleteChar);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.DeleteChar);
 		case ConsoleKey.Insert:
-			return MapKeyModifiers (keyInfo, Key.InsertChar);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.InsertChar);
 
 		case ConsoleKey.Oem1:
 		case ConsoleKey.Oem2:
@@ -1046,79 +1046,85 @@ internal class NetDriver : ConsoleDriver {
 		case ConsoleKey.Oem7:
 		case ConsoleKey.Oem8:
 		case ConsoleKey.Oem102:
+			var ret = ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)keyInfo.KeyChar));
+			if (ret.HasFlag (KeyCode.ShiftMask)) {
+				ret &= ~KeyCode.ShiftMask;
+			}
+			return ret;
+
 		case ConsoleKey.OemPeriod:
 		case ConsoleKey.OemComma:
 		case ConsoleKey.OemPlus:
 		case ConsoleKey.OemMinus:
-			return (Key)((uint)keyInfo.KeyChar);
+			return (KeyCode)((uint)keyInfo.KeyChar);
 		}
 
 		var key = keyInfo.Key;
-		if (key >= ConsoleKey.A && key <= ConsoleKey.Z) {
+		if (key is >= ConsoleKey.A and <= ConsoleKey.Z) {
 			var delta = key - ConsoleKey.A;
 			if (keyInfo.Modifiers == ConsoleModifiers.Control) {
-				return (Key)(((uint)Key.CtrlMask) | ((uint)Key.A + delta));
+				return (KeyCode)(((uint)KeyCode.CtrlMask) | ((uint)KeyCode.A + delta));
 			}
 			if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
-				return (Key)(((uint)Key.AltMask) | ((uint)Key.A + delta));
+				return (KeyCode)(((uint)KeyCode.AltMask) | ((uint)KeyCode.A + delta));
+			}
+			if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) {
+				return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.A + delta));
 			}
 			if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
 				if (keyInfo.KeyChar == 0 || (keyInfo.KeyChar != 0 && keyInfo.KeyChar >= 1 && keyInfo.KeyChar <= 26)) {
-					return MapKeyModifiers (keyInfo, (Key)((uint)Key.A + delta));
+					return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.A + delta));
 				}
 			}
-			return (Key)((uint)keyInfo.KeyChar);
+
+			if (((keyInfo.Modifiers == ConsoleModifiers.Shift) /*^ (keyInfoEx.CapsLock)*/)) {
+				if (keyInfo.KeyChar <= (uint)KeyCode.Z) {
+					return (KeyCode)((uint)KeyCode.A + delta) | KeyCode.ShiftMask;
+				}
+			}
+			// This is buggy because is converting a lower case to a upper case and mustn't
+			//if (((KeyCode)((uint)keyInfo.KeyChar) & KeyCode.Space) == KeyCode.Space) {
+			//	return (KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space;
+			//}
+			return (KeyCode)(uint)keyInfo.KeyChar;
 		}
-		if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9) {
+		if (key is >= ConsoleKey.D0 and <= ConsoleKey.D9) {
 			var delta = key - ConsoleKey.D0;
 			if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
-				return (Key)(((uint)Key.AltMask) | ((uint)Key.D0 + delta));
+				return (KeyCode)(((uint)KeyCode.AltMask) | ((uint)KeyCode.D0 + delta));
 			}
 			if (keyInfo.Modifiers == ConsoleModifiers.Control) {
-				return (Key)(((uint)Key.CtrlMask) | ((uint)Key.D0 + delta));
+				return (KeyCode)(((uint)KeyCode.CtrlMask) | ((uint)KeyCode.D0 + delta));
 			}
 			if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-				if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30 || keyInfo.KeyChar == ((uint)Key.D0 + delta)) {
-					return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta));
+				if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30 || keyInfo.KeyChar == ((uint)KeyCode.D0 + delta)) {
+					return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.D0 + delta));
 				}
 			}
-			return (Key)((uint)keyInfo.KeyChar);
+			return (KeyCode)((uint)keyInfo.KeyChar);
 		}
 		if (key is >= ConsoleKey.F1 and <= ConsoleKey.F12) {
 			var delta = key - ConsoleKey.F1;
 			if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-				return MapKeyModifiers (keyInfo, (Key)((uint)Key.F1 + delta));
+				return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.F1 + delta));
 			}
 
-			return (Key)((uint)Key.F1 + delta);
-		}
-		if (keyInfo.KeyChar != 0) {
-			return MapKeyModifiers (keyInfo, (Key)((uint)keyInfo.KeyChar));
+			return (KeyCode)((uint)KeyCode.F1 + delta);
 		}
 
-		return (Key)(0xffffffff);
-	}
-
-	KeyModifiers _keyModifiers;
-
-	Key MapKeyModifiers (ConsoleKeyInfo keyInfo, Key key)
-	{
-		_keyModifiers ??= new KeyModifiers ();
-		Key keyMod = new Key ();
-		if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0) {
-			keyMod = Key.ShiftMask;
-			_keyModifiers.Shift = true;
-		}
-		if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0) {
-			keyMod |= Key.CtrlMask;
-			_keyModifiers.Ctrl = true;
+		// Is it a key between a..z?
+		if ((char)keyInfo.KeyChar is >= 'a' and <= 'z') {
+			// 'a' should be Key.A
+			return (KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space;
 		}
-		if ((keyInfo.Modifiers & ConsoleModifiers.Alt) != 0) {
-			keyMod |= Key.AltMask;
-			_keyModifiers.Alt = true;
+
+		// Is it a key between A..Z?
+		if (((KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space) is >= KeyCode.A and <= KeyCode.Z) {
+			// It's Key.A...Z.  Make it Key.A | Key.ShiftMask
+			return (KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space | KeyCode.ShiftMask;
 		}
 
-		return keyMod != Key.Null ? keyMod | key : key;
+		return (KeyCode)(uint)keyInfo.KeyChar;
 	}
 
 	volatile bool _winSizeChanging;
@@ -1131,19 +1137,10 @@ internal class NetDriver : ConsoleDriver {
 			if (consoleKeyInfo.Key == ConsoleKey.Packet) {
 				consoleKeyInfo = FromVKPacketToKConsoleKeyInfo (consoleKeyInfo);
 			}
-			_keyModifiers = new KeyModifiers ();
 			var map = MapKey (consoleKeyInfo);
-			if (map == (Key)0xffffffff) {
-				return;
-			}
-			if (map == Key.Null) {
-				OnKeyDown (new KeyEventEventArgs (new KeyEvent (map, _keyModifiers)));
-				OnKeyUp (new KeyEventEventArgs (new KeyEvent (map, _keyModifiers)));
-			} else {
-				OnKeyDown (new KeyEventEventArgs (new KeyEvent (map, _keyModifiers)));
-				OnKeyUp (new KeyEventEventArgs (new KeyEvent (map, _keyModifiers)));
-				OnKeyPressed (new KeyEventEventArgs (new KeyEvent (map, _keyModifiers)));
-			}
+
+			OnKeyDown (new Key (map));
+			OnKeyUp (new Key (map));
 			break;
 		case NetEvents.EventType.Mouse:
 			OnMouseEvent (new MouseEventEventArgs (ToDriverMouse (inputEvent.MouseEvent)));

+ 230 - 278
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -22,6 +22,8 @@ using System.Runtime.InteropServices;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Diagnostics;
+using Terminal.Gui.ConsoleDrivers;
+using static Unix.Terminal.Delegates;
 
 namespace Terminal.Gui;
 
@@ -376,6 +378,8 @@ internal class WindowsConsole {
 		public char UnicodeChar;
 		[FieldOffset (12), MarshalAs (UnmanagedType.U4)]
 		public ControlKeyState dwControlKeyState;
+
+		public override readonly string ToString () => $"[KeyEventRecord({(bKeyDown ? "down" : "up")},{wRepeatCount},{wVirtualKeyCode},{wVirtualScanCode},{new Rune (UnicodeChar).MakePrintable ()},{dwControlKeyState})]";
 	}
 
 	[Flags]
@@ -595,8 +599,30 @@ internal class WindowsConsole {
 			NumLock = numlock;
 			ScrollLock = scrolllock;
 		}
+
+		/// <summary>
+		/// Prints a ConsoleKeyInfoEx structure
+		/// </summary>
+		/// <param name="ex"></param>
+		/// <returns></returns>
+		public readonly string ToString (ConsoleKeyInfoEx ex)
+		{
+			var ke = new Key ((KeyCode)ex.ConsoleKeyInfo.KeyChar);
+			var sb = new StringBuilder ();
+			sb.Append ($"Key: {(KeyCode)ex.ConsoleKeyInfo.Key} ({ex.ConsoleKeyInfo.Key})");
+			sb.Append ((ex.ConsoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0 ? " | Shift" : string.Empty);
+			sb.Append ((ex.ConsoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0 ? " | Control" : string.Empty);
+			sb.Append ((ex.ConsoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0 ? " | Alt" : string.Empty);
+			sb.Append ($", KeyChar: {ke.AsRune.MakePrintable ()} ({(uint)ex.ConsoleKeyInfo.KeyChar}) ");
+			sb.Append ((ex.CapsLock ? "caps," : string.Empty));
+			sb.Append ((ex.NumLock ? "num," : string.Empty));
+			sb.Append ((ex.ScrollLock ? "scroll," : string.Empty));
+			var s = sb.ToString ().TrimEnd (',').TrimEnd (' ');
+			return $"[ConsoleKeyInfoEx({s})]";
+		}
 	}
 
+
 	[DllImport ("kernel32.dll", SetLastError = true)]
 	static extern IntPtr GetStdHandle (int nStdHandle);
 
@@ -875,14 +901,172 @@ internal class WindowsDriver : ConsoleDriver {
 	}
 #endif
 
-	// This is a bit hacky, but it enables users to hold down a key and 
-	// OnKeyDown, OnKeyPressed, OnKeyPressed, OnKeyUp
-	// It might be worth making OnKeyDown and OnKeyUp virtual so this can be tracked from those calls in case
-	// somoene calls them externally??
-	//
-	// It also is broken when modifiers keys are down too
-	//
-	//Key _keyDown = (Key)0xffffffff;
+	KeyCode MapKey (WindowsConsole.ConsoleKeyInfoEx keyInfoEx)
+	{
+		var keyInfo = keyInfoEx.ConsoleKeyInfo;
+		switch (keyInfo.Key) {
+		case ConsoleKey.Escape:
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Esc);
+		case ConsoleKey.Tab:
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Tab);
+		case ConsoleKey.Clear:
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Clear);
+		case ConsoleKey.Home:
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Home);
+		case ConsoleKey.End:
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.End);
+		case ConsoleKey.LeftArrow:
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorLeft);
+		case ConsoleKey.RightArrow:
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorRight);
+		case ConsoleKey.UpArrow:
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorUp);
+		case ConsoleKey.DownArrow:
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorDown);
+		case ConsoleKey.PageUp:
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageUp);
+		case ConsoleKey.PageDown:
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageDown);
+		case ConsoleKey.Enter:
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Enter);
+		case ConsoleKey.Spacebar:
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? KeyCode.Space : (KeyCode)keyInfo.KeyChar);
+		case ConsoleKey.Backspace:
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Backspace);
+		case ConsoleKey.Delete:
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.DeleteChar);
+		case ConsoleKey.Insert:
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.InsertChar);
+		case ConsoleKey.PrintScreen:
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PrintScreen);
+
+		//case ConsoleKey.NumPad0:
+		//	return keyInfoEx.NumLock ? Key.D0 : Key.InsertChar;
+		//case ConsoleKey.NumPad1:
+		//	return keyInfoEx.NumLock ? Key.D1 : Key.End;
+		//case ConsoleKey.NumPad2:
+		//	return keyInfoEx.NumLock ? Key.D2 : Key.CursorDown;
+		//case ConsoleKey.NumPad3:
+		//	return keyInfoEx.NumLock ? Key.D3 : Key.PageDown;
+		//case ConsoleKey.NumPad4:
+		//	return keyInfoEx.NumLock ? Key.D4 : Key.CursorLeft;
+		//case ConsoleKey.NumPad5:
+		//	return keyInfoEx.NumLock ? Key.D5 : (Key)((uint)keyInfo.KeyChar);
+		//case ConsoleKey.NumPad6:
+		//	return keyInfoEx.NumLock ? Key.D6 : Key.CursorRight;
+		//case ConsoleKey.NumPad7:
+		//	return keyInfoEx.NumLock ? Key.D7 : Key.Home;
+		//case ConsoleKey.NumPad8:
+		//	return keyInfoEx.NumLock ? Key.D8 : Key.CursorUp;
+		//case ConsoleKey.NumPad9:
+		//	return keyInfoEx.NumLock ? Key.D9 : Key.PageUp;
+
+		case ConsoleKey.Oem1:
+		case ConsoleKey.Oem2:
+		case ConsoleKey.Oem3:
+		case ConsoleKey.Oem4:
+		case ConsoleKey.Oem5:
+		case ConsoleKey.Oem6:
+		case ConsoleKey.Oem7:
+		case ConsoleKey.Oem8:
+		case ConsoleKey.Oem102:
+			var ret = ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)keyInfo.KeyChar));
+			if (ret.HasFlag (KeyCode.ShiftMask)) {
+				ret &= ~KeyCode.ShiftMask;
+			}
+			return ret;
+
+		case ConsoleKey.OemPeriod:
+		case ConsoleKey.OemComma:
+		case ConsoleKey.OemPlus:
+		case ConsoleKey.OemMinus:
+			return (KeyCode)((uint)keyInfo.KeyChar);
+		}
+
+		var key = keyInfo.Key;
+
+		if (key >= ConsoleKey.A && key <= ConsoleKey.Z) {
+			var delta = key - ConsoleKey.A;
+			if (keyInfo.Modifiers == ConsoleModifiers.Control) {
+				return (KeyCode)(((uint)KeyCode.CtrlMask) | ((uint)KeyCode.A + delta));
+			}
+			if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
+				return (KeyCode)(((uint)KeyCode.AltMask) | ((uint)KeyCode.A + delta));
+			}
+			if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) {
+				return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.A + delta));
+			}
+			if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
+				if (keyInfo.KeyChar == 0 || (keyInfo.KeyChar != 0 && keyInfo.KeyChar >= 1 && keyInfo.KeyChar <= 26)) {
+					return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.A + delta));
+				}
+			}
+
+			if (((keyInfo.Modifiers == ConsoleModifiers.Shift) ^ (keyInfoEx.CapsLock))) {
+				if (keyInfo.KeyChar <= (uint)KeyCode.Z) {
+					return (KeyCode)((uint)KeyCode.A + delta) | KeyCode.ShiftMask;
+				}
+			}
+
+			if (((KeyCode)((uint)keyInfo.KeyChar) & KeyCode.Space) == 0) {
+				return (KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space;
+			}
+
+			if (((KeyCode)((uint)keyInfo.KeyChar) & KeyCode.Space) != 0) {
+				if (((KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space) == (KeyCode)keyInfo.Key) {
+					return (KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space;
+				}
+				return (KeyCode)((uint)keyInfo.KeyChar);
+			}
+
+			return (KeyCode)(uint)keyInfo.KeyChar;
+
+		}
+
+		if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9) {
+			var delta = key - ConsoleKey.D0;
+			if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
+				return (KeyCode)(((uint)KeyCode.AltMask) | ((uint)KeyCode.D0 + delta));
+			}
+			if (keyInfo.Modifiers == ConsoleModifiers.Control) {
+				return (KeyCode)(((uint)KeyCode.CtrlMask) | ((uint)KeyCode.D0 + delta));
+			}
+			if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) {
+				return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.D0 + delta));
+			}
+			if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
+				if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30 || keyInfo.KeyChar == ((uint)KeyCode.D0 + delta)) {
+					return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.D0 + delta));
+				}
+			}
+			return (KeyCode)((uint)keyInfo.KeyChar);
+		}
+
+		if (key >= ConsoleKey.F1 && key <= ConsoleKey.F12) {
+			var delta = key - ConsoleKey.F1;
+			if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
+				return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.F1 + delta));
+			}
+
+			return (KeyCode)((uint)KeyCode.F1 + delta);
+		}
+
+		if (key == (ConsoleKey)16) { // Shift
+			return KeyCode.Null | KeyCode.ShiftMask;
+		}
+
+		if (key == (ConsoleKey)17) { // Ctrl
+			return KeyCode.Null | KeyCode.CtrlMask;
+		}
+
+		if (key == (ConsoleKey)18) { // Alt
+			return KeyCode.Null | KeyCode.AltMask;
+		}
+
+		return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)keyInfo.KeyChar));
+	}
+
+	bool _altDown = false;
 
 	internal void ProcessInput (WindowsConsole.InputRecord inputEvent)
 	{
@@ -892,101 +1076,43 @@ internal class WindowsDriver : ConsoleDriver {
 			if (fromPacketKey) {
 				inputEvent.KeyEvent = FromVKPacketToKeyEventRecord (inputEvent.KeyEvent);
 			}
-			var map = MapKey (ToConsoleKeyInfoEx (inputEvent.KeyEvent));
-			//var ke = inputEvent.KeyEvent;
-			//System.Diagnostics.Debug.WriteLine ($"fromPacketKey: {fromPacketKey}");
-			//if (ke.UnicodeChar == '\0') {
-			//	System.Diagnostics.Debug.WriteLine ("UnicodeChar: 0'\\0'");
-			//} else if (ke.UnicodeChar == 13) {
-			//	System.Diagnostics.Debug.WriteLine ("UnicodeChar: 13'\\n'");
-			//} else {
-			//	System.Diagnostics.Debug.WriteLine ($"UnicodeChar: {(uint)ke.UnicodeChar}'{ke.UnicodeChar}'");
-			//}
-			//System.Diagnostics.Debug.WriteLine ($"bKeyDown: {ke.bKeyDown}");
-			//System.Diagnostics.Debug.WriteLine ($"dwControlKeyState: {ke.dwControlKeyState}");
-			//System.Diagnostics.Debug.WriteLine ($"wRepeatCount: {ke.wRepeatCount}");
-			//System.Diagnostics.Debug.WriteLine ($"wVirtualKeyCode: {ke.wVirtualKeyCode}");
-			//System.Diagnostics.Debug.WriteLine ($"wVirtualScanCode: {ke.wVirtualScanCode}");
-
-			if (map == (Key)0xffffffff) {
-				KeyEvent key = new KeyEvent ();
-
-				// Shift = VK_SHIFT = 0x10
-				// Ctrl = VK_CONTROL = 0x11
-				// Alt = VK_MENU = 0x12
-
-				if (inputEvent.KeyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.CapslockOn)) {
-					inputEvent.KeyEvent.dwControlKeyState &= ~WindowsConsole.ControlKeyState.CapslockOn;
-				}
+			var keyInfo = ToConsoleKeyInfoEx (inputEvent.KeyEvent);
+			Debug.WriteLine ($"event: {inputEvent.ToString ()} {keyInfo.ToString (keyInfo)}");
 
-				if (inputEvent.KeyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.ScrolllockOn)) {
-					inputEvent.KeyEvent.dwControlKeyState &= ~WindowsConsole.ControlKeyState.ScrolllockOn;
-				}
 
-				if (inputEvent.KeyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.NumlockOn)) {
-					inputEvent.KeyEvent.dwControlKeyState &= ~WindowsConsole.ControlKeyState.NumlockOn;
-				}
+			var map = MapKey (keyInfo);
 
-				switch (inputEvent.KeyEvent.dwControlKeyState) {
-				case WindowsConsole.ControlKeyState.RightAltPressed:
-				case WindowsConsole.ControlKeyState.RightAltPressed |
-				    WindowsConsole.ControlKeyState.LeftControlPressed |
-				    WindowsConsole.ControlKeyState.EnhancedKey:
-				case WindowsConsole.ControlKeyState.EnhancedKey:
-					key = new KeyEvent (Key.CtrlMask | Key.AltMask, _keyModifiers);
-					break;
-				case WindowsConsole.ControlKeyState.LeftAltPressed:
-					key = new KeyEvent (Key.AltMask, _keyModifiers);
-					break;
-				case WindowsConsole.ControlKeyState.RightControlPressed:
-				case WindowsConsole.ControlKeyState.LeftControlPressed:
-					key = new KeyEvent (Key.CtrlMask, _keyModifiers);
-					break;
-				case WindowsConsole.ControlKeyState.ShiftPressed:
-					key = new KeyEvent (Key.ShiftMask, _keyModifiers);
-					break;
-				case WindowsConsole.ControlKeyState.NumlockOn:
-					break;
-				case WindowsConsole.ControlKeyState.ScrolllockOn:
-					break;
-				case WindowsConsole.ControlKeyState.CapslockOn:
-					break;
-				default:
-					key = inputEvent.KeyEvent.wVirtualKeyCode switch {
-						0x10 => new KeyEvent (Key.ShiftMask, _keyModifiers),
-						0x11 => new KeyEvent (Key.CtrlMask, _keyModifiers),
-						0x12 => new KeyEvent (Key.AltMask, _keyModifiers),
-						_ => new KeyEvent (Key.Unknown, _keyModifiers)
-					};
-					break;
-				}
-
-				if (inputEvent.KeyEvent.bKeyDown) {
-					//_keyDown = key.Key;
-					OnKeyDown (new KeyEventEventArgs (key));
-				} else {
-					//_keyDown = (Key)0xffffffff;
-					OnKeyUp (new KeyEventEventArgs (key));
-				}
+			if (inputEvent.KeyEvent.bKeyDown) {
+				_altDown = keyInfo.ConsoleKeyInfo.Modifiers == ConsoleModifiers.Alt;
+				// Avoid sending repeat keydowns
+				OnKeyDown (new Key (map));
 			} else {
-				if (inputEvent.KeyEvent.bKeyDown) {
-					// May occurs using SendKeys
-					_keyModifiers ??= new KeyModifiers ();
-
-					//if (_keyDown == (Key)0xffffffff) {
-					// Avoid sending repeat keydowns
-					//	_keyDown = map;
-					OnKeyDown (new KeyEventEventArgs (new KeyEvent (map, _keyModifiers)));
-					//}
-					OnKeyPressed (new KeyEventEventArgs (new KeyEvent (map, _keyModifiers)));
+				var keyPressedEventArgs = new Key (map);
+
+				// PROTOTYPE: This logic enables `Alt` key presses (down, up, pressed).
+				// However, if while the 'Alt' key is down, if another key is pressed and
+				// released, there will be a keypressed event for that and the
+				// keypressed event for just `Alt` will be suppressed. 
+				// This allows MenuBar to have `Alt` as a keybinding
+				if (map != KeyCode.AltMask) {
+					if (keyInfo.ConsoleKeyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt)) {
+						if (_altDown) {
+							_altDown = false;
+							OnKeyUp (new Key (map));
+						}
+
+					}
+					_altDown = false;
+					// KeyUp of an Alt-key press. 
+					OnKeyUp (keyPressedEventArgs);
 				} else {
-					//_keyDown = (Key)0xffffffff;
-					OnKeyUp (new KeyEventEventArgs (new KeyEvent (map, _keyModifiers)));
+					OnKeyUp (keyPressedEventArgs);
+					if (_altDown) {
+						_altDown = false;
+					}
 				}
 			}
-			if (!inputEvent.KeyEvent.bKeyDown && inputEvent.KeyEvent.dwControlKeyState == 0) {
-				_keyModifiers = null;
-			}
+
 			break;
 
 		case WindowsConsole.EventType.Mouse:
@@ -1278,8 +1404,6 @@ internal class WindowsDriver : ConsoleDriver {
 		return mouseFlag;
 	}
 
-	KeyModifiers _keyModifiers;
-
 	public WindowsConsole.ConsoleKeyInfoEx ToConsoleKeyInfoEx (WindowsConsole.KeyEventRecord keyEvent)
 	{
 		var state = keyEvent.dwControlKeyState;
@@ -1287,33 +1411,12 @@ internal class WindowsDriver : ConsoleDriver {
 		var shift = (state & WindowsConsole.ControlKeyState.ShiftPressed) != 0;
 		var alt = (state & (WindowsConsole.ControlKeyState.LeftAltPressed | WindowsConsole.ControlKeyState.RightAltPressed)) != 0;
 		var control = (state & (WindowsConsole.ControlKeyState.LeftControlPressed | WindowsConsole.ControlKeyState.RightControlPressed)) != 0;
-		var capsLock = (state & (WindowsConsole.ControlKeyState.CapslockOn)) != 0;
-		var numLock = (state & (WindowsConsole.ControlKeyState.NumlockOn)) != 0;
-		var scrollLock = (state & (WindowsConsole.ControlKeyState.ScrolllockOn)) != 0;
+		var capslock = (state & WindowsConsole.ControlKeyState.CapslockOn) != 0;
+		var numlock = (state & WindowsConsole.ControlKeyState.NumlockOn) != 0;
+		var scrolllock = (state & WindowsConsole.ControlKeyState.ScrolllockOn) != 0;
 
-		_keyModifiers ??= new KeyModifiers ();
-		if (shift) {
-			_keyModifiers.Shift = true;
-		}
-		if (alt) {
-			_keyModifiers.Alt = true;
-		}
-		if (control) {
-			_keyModifiers.Ctrl = true;
-		}
-		if (capsLock) {
-			_keyModifiers.Capslock = true;
-		}
-		if (numLock) {
-			_keyModifiers.Numlock = true;
-		}
-		if (scrollLock) {
-			_keyModifiers.Scrolllock = true;
-		}
-
-		var consoleKeyInfo = new ConsoleKeyInfo (keyEvent.UnicodeChar, (ConsoleKey)keyEvent.wVirtualKeyCode, shift, alt, control);
-
-		return new WindowsConsole.ConsoleKeyInfoEx (consoleKeyInfo, capsLock, numLock, scrollLock);
+		var cki = new ConsoleKeyInfo (keyEvent.UnicodeChar, (ConsoleKey)keyEvent.wVirtualKeyCode, shift, alt, control);
+		return new WindowsConsole.ConsoleKeyInfoEx (cki, capslock, numlock, scrolllock);
 	}
 
 	public WindowsConsole.KeyEventRecord FromVKPacketToKeyEventRecord (WindowsConsole.KeyEventRecord keyEvent)
@@ -1334,169 +1437,18 @@ internal class WindowsDriver : ConsoleDriver {
 		    keyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.RightControlPressed)) {
 			mod |= ConsoleModifiers.Control;
 		}
-		var keyChar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (keyEvent.UnicodeChar, mod, out uint virtualKey, out uint scanCode);
+		var cKeyInfo = ConsoleKeyMapping.GetConsoleKeyFromKey (keyEvent.UnicodeChar, mod, out uint scanCode);
 
 		return new WindowsConsole.KeyEventRecord {
-			UnicodeChar = (char)keyChar,
+			UnicodeChar = cKeyInfo.KeyChar,
 			bKeyDown = keyEvent.bKeyDown,
 			dwControlKeyState = keyEvent.dwControlKeyState,
 			wRepeatCount = keyEvent.wRepeatCount,
-			wVirtualKeyCode = (ushort)virtualKey,
+			wVirtualKeyCode = (ushort)cKeyInfo.Key,
 			wVirtualScanCode = (ushort)scanCode
 		};
 	}
 
-	public Key MapKey (WindowsConsole.ConsoleKeyInfoEx keyInfoEx)
-	{
-		var keyInfo = keyInfoEx.ConsoleKeyInfo;
-		switch (keyInfo.Key) {
-		case ConsoleKey.Escape:
-			return MapKeyModifiers (keyInfo, Key.Esc);
-		case ConsoleKey.Tab:
-			return keyInfo.Modifiers == ConsoleModifiers.Shift ? Key.BackTab : Key.Tab;
-		case ConsoleKey.Clear:
-			return MapKeyModifiers (keyInfo, Key.Clear);
-		case ConsoleKey.Home:
-			return MapKeyModifiers (keyInfo, Key.Home);
-		case ConsoleKey.End:
-			return MapKeyModifiers (keyInfo, Key.End);
-		case ConsoleKey.LeftArrow:
-			return MapKeyModifiers (keyInfo, Key.CursorLeft);
-		case ConsoleKey.RightArrow:
-			return MapKeyModifiers (keyInfo, Key.CursorRight);
-		case ConsoleKey.UpArrow:
-			return MapKeyModifiers (keyInfo, Key.CursorUp);
-		case ConsoleKey.DownArrow:
-			return MapKeyModifiers (keyInfo, Key.CursorDown);
-		case ConsoleKey.PageUp:
-			return MapKeyModifiers (keyInfo, Key.PageUp);
-		case ConsoleKey.PageDown:
-			return MapKeyModifiers (keyInfo, Key.PageDown);
-		case ConsoleKey.Enter:
-			return MapKeyModifiers (keyInfo, Key.Enter);
-		case ConsoleKey.Spacebar:
-			return MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? Key.Space : (Key)keyInfo.KeyChar);
-		case ConsoleKey.Backspace:
-			return MapKeyModifiers (keyInfo, Key.Backspace);
-		case ConsoleKey.Delete:
-			return MapKeyModifiers (keyInfo, Key.DeleteChar);
-		case ConsoleKey.Insert:
-			return MapKeyModifiers (keyInfo, Key.InsertChar);
-		case ConsoleKey.PrintScreen:
-			return MapKeyModifiers (keyInfo, Key.PrintScreen);
-
-		case ConsoleKey.NumPad0:
-			return keyInfoEx.NumLock ? Key.D0 : Key.InsertChar;
-		case ConsoleKey.NumPad1:
-			return keyInfoEx.NumLock ? Key.D1 : Key.End;
-		case ConsoleKey.NumPad2:
-			return keyInfoEx.NumLock ? Key.D2 : Key.CursorDown;
-		case ConsoleKey.NumPad3:
-			return keyInfoEx.NumLock ? Key.D3 : Key.PageDown;
-		case ConsoleKey.NumPad4:
-			return keyInfoEx.NumLock ? Key.D4 : Key.CursorLeft;
-		case ConsoleKey.NumPad5:
-			return keyInfoEx.NumLock ? Key.D5 : (Key)((uint)keyInfo.KeyChar);
-		case ConsoleKey.NumPad6:
-			return keyInfoEx.NumLock ? Key.D6 : Key.CursorRight;
-		case ConsoleKey.NumPad7:
-			return keyInfoEx.NumLock ? Key.D7 : Key.Home;
-		case ConsoleKey.NumPad8:
-			return keyInfoEx.NumLock ? Key.D8 : Key.CursorUp;
-		case ConsoleKey.NumPad9:
-			return keyInfoEx.NumLock ? Key.D9 : Key.PageUp;
-
-		case ConsoleKey.Oem1:
-		case ConsoleKey.Oem2:
-		case ConsoleKey.Oem3:
-		case ConsoleKey.Oem4:
-		case ConsoleKey.Oem5:
-		case ConsoleKey.Oem6:
-		case ConsoleKey.Oem7:
-		case ConsoleKey.Oem8:
-		case ConsoleKey.Oem102:
-		case ConsoleKey.OemPeriod:
-		case ConsoleKey.OemComma:
-		case ConsoleKey.OemPlus:
-		case ConsoleKey.OemMinus:
-			if (keyInfo.KeyChar == 0) {
-				return Key.Unknown;
-			}
-
-			return (Key)((uint)keyInfo.KeyChar);
-		}
-
-		var key = keyInfo.Key;
-		//var alphaBase = ((keyInfo.Modifiers == ConsoleModifiers.Shift) ^ (keyInfoEx.CapsLock)) ? 'A' : 'a';
-
-		if (key >= ConsoleKey.A && key <= ConsoleKey.Z) {
-			var delta = key - ConsoleKey.A;
-			if (keyInfo.Modifiers == ConsoleModifiers.Control) {
-				return (Key)(((uint)Key.CtrlMask) | ((uint)Key.A + delta));
-			}
-			if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
-				return (Key)(((uint)Key.AltMask) | ((uint)Key.A + delta));
-			}
-			if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) {
-				return MapKeyModifiers (keyInfo, (Key)((uint)Key.A + delta));
-			}
-			if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-				if (keyInfo.KeyChar == 0 || (keyInfo.KeyChar != 0 && keyInfo.KeyChar >= 1 && keyInfo.KeyChar <= 26)) {
-					return MapKeyModifiers (keyInfo, (Key)((uint)Key.A + delta));
-				}
-			}
-			//return (Key)((uint)alphaBase + delta);
-			return (Key)((uint)keyInfo.KeyChar);
-		}
-		if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9) {
-			var delta = key - ConsoleKey.D0;
-			if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
-				return (Key)(((uint)Key.AltMask) | ((uint)Key.D0 + delta));
-			}
-			if (keyInfo.Modifiers == ConsoleModifiers.Control) {
-				return (Key)(((uint)Key.CtrlMask) | ((uint)Key.D0 + delta));
-			}
-			if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) {
-				return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta));
-			}
-			if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-				if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30 || keyInfo.KeyChar == ((uint)Key.D0 + delta)) {
-					return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta));
-				}
-			}
-			return (Key)((uint)keyInfo.KeyChar);
-		}
-		if (key >= ConsoleKey.F1 && key <= ConsoleKey.F12) {
-			var delta = key - ConsoleKey.F1;
-			if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-				return MapKeyModifiers (keyInfo, (Key)((uint)Key.F1 + delta));
-			}
-
-			return (Key)((uint)Key.F1 + delta);
-		}
-		if (keyInfo.KeyChar != 0) {
-			return MapKeyModifiers (keyInfo, (Key)((uint)keyInfo.KeyChar));
-		}
-
-		return (Key)(0xffffffff);
-	}
-
-	private Key MapKeyModifiers (ConsoleKeyInfo keyInfo, Key key)
-	{
-		Key keyMod = new Key ();
-		if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0) {
-			keyMod = Key.ShiftMask;
-		}
-		if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0) {
-			keyMod |= Key.CtrlMask;
-		}
-		if ((keyInfo.Modifiers & ConsoleModifiers.Alt) != 0) {
-			keyMod |= Key.AltMask;
-		}
-
-		return keyMod != Key.Null ? keyMod | key : key;
-	}
-
 	public override bool IsRuneSupported (Rune rune)
 	{
 		return base.IsRuneSupported (rune) && rune.IsBmp;

+ 1 - 0
Terminal.Gui/Drawing/Thickness.cs

@@ -108,6 +108,7 @@ namespace Terminal.Gui {
 		/// Gets whether the specified coordinates lie within the thickness (inside the bounding rectangle but outside of
 		/// the rectangle described by <see cref="GetInside(Rect)"/>.
 		/// </summary>
+		/// <param name="outside">Describes the location and size of the rectangle that contains the thickness.</param>
 		/// <param name="x"></param>
 		/// <param name="y"></param>
 		/// <returns><see langword="true"/> if the specified coordinate is within the thickness; <see langword="false"/> otherwise.</returns>

+ 418 - 391
Terminal.Gui/Input/Command.cs

@@ -1,392 +1,419 @@
-// These classes use a keybinding system based on the design implemented in Scintilla.Net which is an MIT licensed open source project https://github.com/jacobslusser/ScintillaNET/blob/master/src/ScintillaNET/Command.cs
-
-using System;
-
-namespace Terminal.Gui {
-
-	/// <summary>
-	/// Actions which can be performed by the application or bound to keys in a <see cref="View"/> control.
-	/// </summary>
-	public enum Command {
-
-		/// <summary>
-		/// Moves down one item (cell, line, etc...).
-		/// </summary>
-		LineDown,
-
-		/// <summary>
-		/// Extends the selection down one (cell, line, etc...).
-		/// </summary>
-		LineDownExtend,
-
-		/// <summary>
-		/// Moves down to the last child node of the branch that holds the current selection.
-		/// </summary>
-		LineDownToLastBranch,
-
-		/// <summary>
-		/// Scrolls down one (cell, line, etc...) (without changing the selection).
-		/// </summary>
-		ScrollDown,
-
-		// --------------------------------------------------------------------
-
-		/// <summary>
-		/// Moves up one (cell, line, etc...).
-		/// </summary>
-		LineUp,
-
-		/// <summary>
-		/// Extends the selection up one item (cell, line, etc...).
-		/// </summary>
-		LineUpExtend,
-
-		/// <summary>
-		/// Moves up to the first child node of the branch that holds the current selection.
-		/// </summary>
-		LineUpToFirstBranch,
-
-		/// <summary>
-		/// Scrolls up one item (cell, line, etc...) (without changing the selection).
-		/// </summary>
-		ScrollUp,
-
-		/// <summary>
-		/// Moves the selection left one by the minimum increment supported by the <see cref="View"/> e.g. single character, cell, item etc.
-		/// </summary>
-		Left,
-
-		/// <summary>
-		/// Scrolls one item (cell, character, etc...) to the left
-		/// </summary>
-		ScrollLeft,
-
-		/// <summary>
-		/// Extends the selection left one by the minimum increment supported by the view e.g. single character, cell, item etc.
-		/// </summary>
-		LeftExtend,
-
-		/// <summary>
-		/// Moves the selection right one by the minimum increment supported by the view e.g. single character, cell, item etc.
-		/// </summary>
-		Right,
-
-		/// <summary>
-		/// Scrolls one item (cell, character, etc...) to the right.
-		/// </summary>
-		ScrollRight,
-
-		/// <summary>
-		/// Extends the selection right one by the minimum increment supported by the view e.g. single character, cell, item etc.
-		/// </summary>
-		RightExtend,
-
-		/// <summary>
-		/// Moves the caret to the start of the previous word.
-		/// </summary>
-		WordLeft,
-
-		/// <summary>
-		/// Extends the selection to the start of the previous word.
-		/// </summary>
-		WordLeftExtend,
-
-		/// <summary>
-		/// Moves the caret to the start of the next word.
-		/// </summary>
-		WordRight,
-
-		/// <summary>
-		/// Extends the selection to the start of the next word.
-		/// </summary>
-		WordRightExtend,
-
-		/// <summary>
-		/// Cuts to the clipboard the characters from the current position to the end of the line.
-		/// </summary>
-		CutToEndLine,
-
-		/// <summary>
-		/// Cuts to the clipboard the characters from the current position to the start of the line.
-		/// </summary>
-		CutToStartLine,
-
-		/// <summary>
-		/// Deletes the characters forwards.
-		/// </summary>
-		KillWordForwards,
-
-		/// <summary>
-		/// Deletes the characters backwards.
-		/// </summary>
-		KillWordBackwards,
-
-		/// <summary>
-		/// Toggles overwrite mode such that newly typed text overwrites the text that is
-		/// already there (typically associated with the Insert key).
-		/// </summary>
-		ToggleOverwrite,
-
-		/// <summary>
-		/// Enables overwrite mode such that newly typed text overwrites the text that is
-		/// already there (typically associated with the Insert key).
-		/// </summary>
-		EnableOverwrite,
-
-		/// <summary>
-		/// Disables overwrite mode (<see cref="EnableOverwrite"/>)
-		/// </summary>
-		DisableOverwrite,
-
-		/// <summary>
-		/// Move one page down.
-		/// </summary>
-		PageDown,
-
-		/// <summary>
-		/// Move one page page extending the selection to cover revealed objects/characters.
-		/// </summary>
-		PageDownExtend,
-
-		/// <summary>
-		/// Move one page up.
-		/// </summary>
-		PageUp,
-
-		/// <summary>
-		/// Move one page up extending the selection to cover revealed objects/characters.
-		/// </summary>
-		PageUpExtend,
-
-		/// <summary>
-		/// Moves to the top/home.
-		/// </summary>
-		TopHome,
-
-		/// <summary>
-		/// Extends the selection to the top/home.
-		/// </summary>
-		TopHomeExtend,
-
-		/// <summary>
-		/// Moves to the bottom/end.
-		/// </summary>
-		BottomEnd,
-
-		/// <summary>
-		/// Extends the selection to the bottom/end.
-		/// </summary>
-		BottomEndExtend,
-
-		/// <summary>
-		/// Open the selected item.
-		/// </summary>
-		OpenSelectedItem,
-
-		/// <summary>
-		/// Toggle the checked state.
-		/// </summary>
-		ToggleChecked,
-
-		/// <summary>
-		/// Accepts the current state (e.g. selection, button press etc).
-		/// </summary>
-		Accept,
-
-		/// <summary>
-		/// Toggles the Expanded or collapsed state of a a list or item (with subitems).
-		/// </summary>
-		ToggleExpandCollapse,
-
-		/// <summary>
-		/// Expands a list or item (with subitems).
-		/// </summary>
-		Expand,
-
-		/// <summary>
-		/// Recursively Expands all child items and their child items (if any).
-		/// </summary>
-		ExpandAll,
-
-		/// <summary>
-		/// Collapses a list or item (with subitems).
-		/// </summary>
-		Collapse,
-
-		/// <summary>
-		/// Recursively collapses a list items of their children (if any).
-		/// </summary>
-		CollapseAll,
-
-		/// <summary>
-		/// Cancels an action or any temporary states on the control e.g. expanding
-		/// a combo list.
-		/// </summary>
-		Cancel,
-
-		/// <summary>
-		/// Unix emulation.
-		/// </summary>
-		UnixEmulation,
-
-		/// <summary>
-		/// Deletes the character on the right.
-		/// </summary>
-		DeleteCharRight,
-
-		/// <summary>
-		/// Deletes the character on the left.
-		/// </summary>
-		DeleteCharLeft,
-
-		/// <summary>
-		/// Selects all objects.
-		/// </summary>
-		SelectAll,
-
-		/// <summary>
-		/// Deletes all objects.
-		/// </summary>
-		DeleteAll,
-
-		/// <summary>
-		/// Moves the cursor to the start of line.
-		/// </summary>
-		StartOfLine,
-
-		/// <summary>
-		/// Extends the selection to the start of line.
-		/// </summary>
-		StartOfLineExtend,
-
-		/// <summary>
-		/// Moves the cursor to the end of line.
-		/// </summary>
-		EndOfLine,
-
-		/// <summary>
-		/// Extends the selection to the end of line.
-		/// </summary>
-		EndOfLineExtend,
-
-		/// <summary>
-		/// Moves the cursor to the top of page.
-		/// </summary>
-		StartOfPage,
-
-		/// <summary>
-		/// Moves the cursor to the bottom of page.
-		/// </summary>
-		EndOfPage,
-
-		/// <summary>
-		/// Moves to the left page.
-		/// </summary>
-		PageLeft,
-
-		/// <summary>
-		/// Moves to the right page.
-		/// </summary>
-		PageRight,
-
-		/// <summary>
-		/// Moves to the left begin.
-		/// </summary>
-		LeftHome,
-
-		/// <summary>
-		/// Extends the selection to the left begin.
-		/// </summary>
-		LeftHomeExtend,
-
-		/// <summary>
-		/// Moves to the right end.
-		/// </summary>
-		RightEnd,
-
-		/// <summary>
-		/// Extends the selection to the right end.
-		/// </summary>
-		RightEndExtend,
-
-		/// <summary>
-		/// Undo changes.
-		/// </summary>
-		Undo,
-
-		/// <summary>
-		/// Redo changes.
-		/// </summary>
-		Redo,
-
-		/// <summary>
-		/// Copies the current selection.
-		/// </summary>
-		Copy,
-
-		/// <summary>
-		/// Cuts the current selection.
-		/// </summary>
-		Cut,
-
-		/// <summary>
-		/// Pastes the current selection.
-		/// </summary>
-		Paste,
-
-		/// <summary>
-		/// Quit a <see cref="Toplevel"/>.
-		/// </summary>
-		QuitToplevel,
-
-		/// <summary>
-		/// Suspend a application (Only implemented in <see cref="CursesDriver"/>).
-		/// </summary>
-		Suspend,
-
-		/// <summary>
-		/// Moves focus to the next view.
-		/// </summary>
-		NextView,
-
-		/// <summary>
-		/// Moves focuss to the previous view.
-		/// </summary>
-		PreviousView,
-
-		/// <summary>
-		/// Moves focus to the next view or Toplevel (case of Overlapped).
-		/// </summary>
-		NextViewOrTop,
-
-		/// <summary>
-		/// Moves focus to the next previous or Toplevel (case of Overlapped).
-		/// </summary>
-		PreviousViewOrTop,
-
-		/// <summary>
-		/// Refresh.
-		/// </summary>
-		Refresh,
-
-		/// <summary>
-		/// Toggles the selection.
-		/// </summary>
-		ToggleExtend,
-
-		/// <summary>
-		/// Inserts a new item.
-		/// </summary>
-		NewLine,
-
-		/// <summary>
-		/// Tabs to the next item.
-		/// </summary>
-		Tab,
-
-		/// <summary>
-		/// Tabs back to the previous item.
-		/// </summary>
-		BackTab
-	}
+// These classes use a keybinding system based on the design implemented in Scintilla.Net which is an
+// MIT licensed open source project https://github.com/jacobslusser/ScintillaNET/blob/master/src/ScintillaNET/Command.cs
+
+namespace Terminal.Gui;
+
+/// <summary>
+/// Actions which can be performed by the application or bound to keys in a <see cref="View"/> control.
+/// </summary>
+public enum Command {
+	/// <summary>
+	/// The default command. For <see cref="View"/> this focuses the view.
+	/// </summary>
+	Default,
+
+	/// <summary>
+	/// Moves down one item (cell, line, etc...).
+	/// </summary>
+	LineDown,
+
+	/// <summary>
+	/// Extends the selection down one (cell, line, etc...).
+	/// </summary>
+	LineDownExtend,
+
+	/// <summary>
+	/// Moves down to the last child node of the branch that holds the current selection.
+	/// </summary>
+	LineDownToLastBranch,
+
+	/// <summary>
+	/// Scrolls down one (cell, line, etc...) (without changing the selection).
+	/// </summary>
+	ScrollDown,
+
+	// --------------------------------------------------------------------
+
+	/// <summary>
+	/// Moves up one (cell, line, etc...).
+	/// </summary>
+	LineUp,
+
+	/// <summary>
+	/// Extends the selection up one item (cell, line, etc...).
+	/// </summary>
+	LineUpExtend,
+
+	/// <summary>
+	/// Moves up to the first child node of the branch that holds the current selection.
+	/// </summary>
+	LineUpToFirstBranch,
+
+	/// <summary>
+	/// Scrolls up one item (cell, line, etc...) (without changing the selection).
+	/// </summary>
+	ScrollUp,
+
+	/// <summary>
+	/// Moves the selection left one by the minimum increment supported by the <see cref="View"/> e.g. single character, cell, item etc.
+	/// </summary>
+	Left,
+
+	/// <summary>
+	/// Scrolls one item (cell, character, etc...) to the left
+	/// </summary>
+	ScrollLeft,
+
+	/// <summary>
+	/// Extends the selection left one by the minimum increment supported by the view e.g. single character, cell, item etc.
+	/// </summary>
+	LeftExtend,
+
+	/// <summary>
+	/// Moves the selection right one by the minimum increment supported by the view e.g. single character, cell, item etc.
+	/// </summary>
+	Right,
+
+	/// <summary>
+	/// Scrolls one item (cell, character, etc...) to the right.
+	/// </summary>
+	ScrollRight,
+
+	/// <summary>
+	/// Extends the selection right one by the minimum increment supported by the view e.g. single character, cell, item etc.
+	/// </summary>
+	RightExtend,
+
+	/// <summary>
+	/// Moves the caret to the start of the previous word.
+	/// </summary>
+	WordLeft,
+
+	/// <summary>
+	/// Extends the selection to the start of the previous word.
+	/// </summary>
+	WordLeftExtend,
+
+	/// <summary>
+	/// Moves the caret to the start of the next word.
+	/// </summary>
+	WordRight,
+
+	/// <summary>
+	/// Extends the selection to the start of the next word.
+	/// </summary>
+	WordRightExtend,
+
+	/// <summary>
+	/// Cuts to the clipboard the characters from the current position to the end of the line.
+	/// </summary>
+	CutToEndLine,
+
+	/// <summary>
+	/// Cuts to the clipboard the characters from the current position to the start of the line.
+	/// </summary>
+	CutToStartLine,
+
+	/// <summary>
+	/// Deletes the characters forwards.
+	/// </summary>
+	KillWordForwards,
+
+	/// <summary>
+	/// Deletes the characters backwards.
+	/// </summary>
+	KillWordBackwards,
+
+	/// <summary>
+	/// Toggles overwrite mode such that newly typed text overwrites the text that is
+	/// already there (typically associated with the Insert key).
+	/// </summary>
+	ToggleOverwrite,
+
+	/// <summary>
+	/// Enables overwrite mode such that newly typed text overwrites the text that is
+	/// already there (typically associated with the Insert key).
+	/// </summary>
+	EnableOverwrite,
+
+	/// <summary>
+	/// Disables overwrite mode (<see cref="EnableOverwrite"/>)
+	/// </summary>
+	DisableOverwrite,
+
+	/// <summary>
+	/// Move one page down.
+	/// </summary>
+	PageDown,
+
+	/// <summary>
+	/// Move one page page extending the selection to cover revealed objects/characters.
+	/// </summary>
+	PageDownExtend,
+
+	/// <summary>
+	/// Move one page up.
+	/// </summary>
+	PageUp,
+
+	/// <summary>
+	/// Move one page up extending the selection to cover revealed objects/characters.
+	/// </summary>
+	PageUpExtend,
+
+	/// <summary>
+	/// Moves to the top/home.
+	/// </summary>
+	TopHome,
+
+	/// <summary>
+	/// Extends the selection to the top/home.
+	/// </summary>
+	TopHomeExtend,
+
+	/// <summary>
+	/// Moves to the bottom/end.
+	/// </summary>
+	BottomEnd,
+
+	/// <summary>
+	/// Extends the selection to the bottom/end.
+	/// </summary>
+	BottomEndExtend,
+
+	/// <summary>
+	/// Open the selected item.
+	/// </summary>
+	OpenSelectedItem,
+
+	/// <summary>
+	/// Toggle the checked state.
+	/// </summary>
+	ToggleChecked,
+
+	/// <summary>
+	/// Accepts the current state (e.g. selection, button press etc).
+	/// </summary>
+	Accept,
+
+	/// <summary>
+	/// Toggles the Expanded or collapsed state of a a list or item (with subitems).
+	/// </summary>
+	ToggleExpandCollapse,
+
+	/// <summary>
+	/// Expands a list or item (with subitems).
+	/// </summary>
+	Expand,
+
+	/// <summary>
+	/// Recursively Expands all child items and their child items (if any).
+	/// </summary>
+	ExpandAll,
+
+	/// <summary>
+	/// Collapses a list or item (with subitems).
+	/// </summary>
+	Collapse,
+
+	/// <summary>
+	/// Recursively collapses a list items of their children (if any).
+	/// </summary>
+	CollapseAll,
+
+	/// <summary>
+	/// Cancels an action or any temporary states on the control e.g. expanding
+	/// a combo list.
+	/// </summary>
+	Cancel,
+
+	/// <summary>
+	/// Unix emulation.
+	/// </summary>
+	UnixEmulation,
+
+	/// <summary>
+	/// Deletes the character on the right.
+	/// </summary>
+	DeleteCharRight,
+
+	/// <summary>
+	/// Deletes the character on the left.
+	/// </summary>
+	DeleteCharLeft,
+
+	/// <summary>
+	/// Selects all objects.
+	/// </summary>
+	SelectAll,
+
+	/// <summary>
+	/// Deletes all objects.
+	/// </summary>
+	DeleteAll,
+
+	/// <summary>
+	/// Moves the cursor to the start of line.
+	/// </summary>
+	StartOfLine,
+
+	/// <summary>
+	/// Extends the selection to the start of line.
+	/// </summary>
+	StartOfLineExtend,
+
+	/// <summary>
+	/// Moves the cursor to the end of line.
+	/// </summary>
+	EndOfLine,
+
+	/// <summary>
+	/// Extends the selection to the end of line.
+	/// </summary>
+	EndOfLineExtend,
+
+	/// <summary>
+	/// Moves the cursor to the top of page.
+	/// </summary>
+	StartOfPage,
+
+	/// <summary>
+	/// Moves the cursor to the bottom of page.
+	/// </summary>
+	EndOfPage,
+
+	/// <summary>
+	/// Moves to the left page.
+	/// </summary>
+	PageLeft,
+
+	/// <summary>
+	/// Moves to the right page.
+	/// </summary>
+	PageRight,
+
+	/// <summary>
+	/// Moves to the left begin.
+	/// </summary>
+	LeftHome,
+
+	/// <summary>
+	/// Extends the selection to the left begin.
+	/// </summary>
+	LeftHomeExtend,
+
+	/// <summary>
+	/// Moves to the right end.
+	/// </summary>
+	RightEnd,
+
+	/// <summary>
+	/// Extends the selection to the right end.
+	/// </summary>
+	RightEndExtend,
+
+	/// <summary>
+	/// Undo changes.
+	/// </summary>
+	Undo,
+
+	/// <summary>
+	/// Redo changes.
+	/// </summary>
+	Redo,
+
+	/// <summary>
+	/// Copies the current selection.
+	/// </summary>
+	Copy,
+
+	/// <summary>
+	/// Cuts the current selection.
+	/// </summary>
+	Cut,
+
+	/// <summary>
+	/// Pastes the current selection.
+	/// </summary>
+	Paste,
+
+	/// <summary>
+	/// Quit a <see cref="Toplevel"/>.
+	/// </summary>
+	QuitToplevel,
+
+	/// <summary>
+	/// Suspend a application (Only implemented in <see cref="CursesDriver"/>).
+	/// </summary>
+	Suspend,
+
+	/// <summary>
+	/// Moves focus to the next view.
+	/// </summary>
+	NextView,
+
+	/// <summary>
+	/// Moves focuss to the previous view.
+	/// </summary>
+	PreviousView,
+
+	/// <summary>
+	/// Moves focus to the next view or Toplevel (case of Overlapped).
+	/// </summary>
+	NextViewOrTop,
+
+	/// <summary>
+	/// Moves focus to the next previous or Toplevel (case of Overlapped).
+	/// </summary>
+	PreviousViewOrTop,
+
+	/// <summary>
+	/// Refresh.
+	/// </summary>
+	Refresh,
+
+	/// <summary>
+	/// Toggles the selection.
+	/// </summary>
+	ToggleExtend,
+
+	/// <summary>
+	/// Inserts a new item.
+	/// </summary>
+	NewLine,
+
+	/// <summary>
+	/// Tabs to the next item.
+	/// </summary>
+	Tab,
+
+	/// <summary>
+	/// Tabs back to the previous item.
+	/// </summary>
+	BackTab,
+
+	/// <summary>
+	/// Saves the current document.
+	/// </summary>
+	Save,
+
+	/// <summary>
+	/// Saves the current document with a new name.
+	/// </summary>
+	SaveAs,
+
+	/// <summary>
+	/// Creates a new document.
+	/// </summary>
+	New,
+
+	/// <summary>
+	/// Moves selection to an item (e.g. highlighting a different menu item) without necessarily accepting it.
+	/// </summary>
+	Select,
+
+	/// <summary>
+	/// Shows context about the item (e.g. a context menu).
+	/// </summary>
+	ShowContextMenu
 }

+ 0 - 560
Terminal.Gui/Input/ConsoleKeyMapping.cs

@@ -1,560 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-
-namespace Terminal.Gui {
-	/// <summary>
-	/// Helper class to handle the scan code and virtual key from a <see cref="ConsoleKey"/>.
-	/// </summary>
-	public static class ConsoleKeyMapping {
-		private class ScanCodeMapping : IEquatable<ScanCodeMapping> {
-			public uint ScanCode;
-			public uint VirtualKey;
-			public ConsoleModifiers Modifiers;
-			public uint UnicodeChar;
-
-			public ScanCodeMapping (uint scanCode, uint virtualKey, ConsoleModifiers modifiers, uint unicodeChar)
-			{
-				ScanCode = scanCode;
-				VirtualKey = virtualKey;
-				Modifiers = modifiers;
-				UnicodeChar = unicodeChar;
-			}
-
-			public bool Equals (ScanCodeMapping other)
-			{
-				return (this.ScanCode.Equals (other.ScanCode) &&
-					this.VirtualKey.Equals (other.VirtualKey) &&
-					this.Modifiers.Equals (other.Modifiers) &&
-					this.UnicodeChar.Equals (other.UnicodeChar));
-			}
-		}
-
-		private static ConsoleModifiers GetModifiers (uint unicodeChar, ConsoleModifiers modifiers, bool isConsoleKey)
-		{
-			if (modifiers.HasFlag (ConsoleModifiers.Shift) &&
-				!modifiers.HasFlag (ConsoleModifiers.Alt) &&
-				!modifiers.HasFlag (ConsoleModifiers.Control)) {
-
-				return ConsoleModifiers.Shift;
-			} else if (modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) {
-				return modifiers;
-			} else if ((!isConsoleKey || (isConsoleKey && (modifiers.HasFlag (ConsoleModifiers.Shift) ||
-				modifiers.HasFlag (ConsoleModifiers.Alt) || modifiers.HasFlag (ConsoleModifiers.Control)))) &&
-				unicodeChar >= 65 && unicodeChar <= 90) {
-
-				return ConsoleModifiers.Shift;
-			}
-			return 0;
-		}
-
-		private static ScanCodeMapping GetScanCode (string propName, uint keyValue, ConsoleModifiers modifiers)
-		{
-			switch (propName) {
-			case "UnicodeChar":
-				var sCode = scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyValue && e.Modifiers == modifiers);
-				if (sCode == null && modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) {
-					return scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyValue && e.Modifiers == 0);
-				}
-				return sCode;
-			case "VirtualKey":
-				sCode = scanCodes.FirstOrDefault ((e) => e.VirtualKey == keyValue && e.Modifiers == modifiers);
-				if (sCode == null && modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) {
-					return scanCodes.FirstOrDefault ((e) => e.VirtualKey == keyValue && e.Modifiers == 0);
-				}
-				return sCode;
-			}
-			
-			return null;
-		}
-
-		/// <summary>
-		/// Gets the <see cref="ConsoleKey"/> from the provided <see cref="Key"/>.
-		/// </summary>
-		/// <param name="key"></param>
-		/// <returns></returns>
-		public static ConsoleKey GetConsoleKeyFromKey (Key key)
-		{
-			ConsoleModifiers mod = new ConsoleModifiers ();
-			if (key.HasFlag (Key.ShiftMask)) {
-				mod |= ConsoleModifiers.Shift;
-			}
-			if (key.HasFlag (Key.AltMask)) {
-				mod |= ConsoleModifiers.Alt;
-			}
-			if (key.HasFlag (Key.CtrlMask)) {
-				mod |= ConsoleModifiers.Control;
-			}
-			return (ConsoleKey)ConsoleKeyMapping.GetConsoleKeyFromKey ((uint)(key & ~Key.CtrlMask & ~Key.ShiftMask & ~Key.AltMask), mod, out _, out _);
-		}
-
-		/// <summary>
-		/// Get the <see cref="ConsoleKey"/> from a <see cref="Key"/>.
-		/// </summary>
-		/// <param name="keyValue">The key value.</param>
-		/// <param name="modifiers">The modifiers keys.</param>
-		/// <param name="scanCode">The resulting scan code.</param>
-		/// <param name="outputChar">The resulting output character.</param>
-		/// <returns>The <see cref="ConsoleKey"/> or the <paramref name="outputChar"/>.</returns>
-		public static uint GetConsoleKeyFromKey (uint keyValue, ConsoleModifiers modifiers, out uint scanCode, out uint outputChar)
-		{
-			scanCode = 0;
-			outputChar = keyValue;
-			if (keyValue == 0) {
-				return 0;
-			}
-
-			uint consoleKey = MapKeyToConsoleKey (keyValue, out bool mappable);
-			if (mappable) {
-				var mod = GetModifiers (keyValue, modifiers, false);
-				var scode = GetScanCode ("UnicodeChar", keyValue, mod);
-				if (scode != null) {
-					consoleKey = scode.VirtualKey;
-					scanCode = scode.ScanCode;
-					outputChar = scode.UnicodeChar;
-				} else {
-					consoleKey = consoleKey < 0xff ? (uint)(consoleKey & 0xff | 0xff << 8) : consoleKey;
-				}
-			} else {
-				var mod = GetModifiers (keyValue, modifiers, false);
-				var scode = GetScanCode ("VirtualKey", consoleKey, mod);
-				if (scode != null) {
-					consoleKey = scode.VirtualKey;
-					scanCode = scode.ScanCode;
-					outputChar = scode.UnicodeChar;
-				}
-			}
-
-			return consoleKey;
-		}
-
-		/// <summary>
-		/// Get the output character from the <see cref="ConsoleKey"/>.
-		/// </summary>
-		/// <param name="unicodeChar">The unicode character.</param>
-		/// <param name="modifiers">The modifiers keys.</param>
-		/// <param name="consoleKey">The resulting console key.</param>
-		/// <param name="scanCode">The resulting scan code.</param>
-		/// <returns>The output character or the <paramref name="consoleKey"/>.</returns>
-		public static uint GetKeyCharFromConsoleKey (uint unicodeChar, ConsoleModifiers modifiers, out uint consoleKey, out uint scanCode)
-		{
-			uint decodedChar = unicodeChar >> 8 == 0xff ? unicodeChar & 0xff : unicodeChar;
-			uint keyChar = decodedChar;
-			consoleKey = 0;
-			var mod = GetModifiers (decodedChar, modifiers, true);
-			scanCode = 0;
-			var scode = unicodeChar != 0 && unicodeChar >> 8 != 0xff ? GetScanCode ("VirtualKey", decodedChar, mod) : null;
-			if (scode != null) {
-				consoleKey = scode.VirtualKey;
-				keyChar = scode.UnicodeChar;
-				scanCode = scode.ScanCode;
-			}
-			if (scode == null) {
-				scode = unicodeChar != 0 ? GetScanCode ("UnicodeChar", decodedChar, mod) : null;
-				if (scode != null) {
-					consoleKey = scode.VirtualKey;
-					keyChar = scode.UnicodeChar;
-					scanCode = scode.ScanCode;
-				}
-			}
-			if (decodedChar != 0 && scanCode == 0 && char.IsLetter ((char)decodedChar)) {
-				string stFormD = ((char)decodedChar).ToString ().Normalize (System.Text.NormalizationForm.FormD);
-				for (int i = 0; i < stFormD.Length; i++) {
-					UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory (stFormD [i]);
-					if (uc != UnicodeCategory.NonSpacingMark && uc != UnicodeCategory.OtherLetter) {
-						consoleKey = char.ToUpper (stFormD [i]);
-						scode = GetScanCode ("VirtualKey", char.ToUpper (stFormD [i]), 0);
-						if (scode != null) {
-							scanCode = scode.ScanCode;
-						}
-					}
-				}
-			}
-
-			return keyChar;
-		}
-
-		/// <summary>
-		/// Maps a <see cref="Key"/> to a <see cref="ConsoleKey"/>.
-		/// </summary>
-		/// <param name="keyValue">The key value.</param>
-		/// <param name="isMappable">If <see langword="true"/> is mapped to a valid character, otherwise <see langword="false"/>.</param>
-		/// <returns>The <see cref="ConsoleKey"/> or the <paramref name="keyValue"/>.</returns>
-		public static uint MapKeyToConsoleKey (uint keyValue, out bool isMappable)
-		{
-			isMappable = false;
-
-			switch ((Key)keyValue) {
-			case Key.Delete:
-				return (uint)ConsoleKey.Delete;
-			case Key.CursorUp:
-				return (uint)ConsoleKey.UpArrow;
-			case Key.CursorDown:
-				return (uint)ConsoleKey.DownArrow;
-			case Key.CursorLeft:
-				return (uint)ConsoleKey.LeftArrow;
-			case Key.CursorRight:
-				return (uint)ConsoleKey.RightArrow;
-			case Key.PageUp:
-				return (uint)ConsoleKey.PageUp;
-			case Key.PageDown:
-				return (uint)ConsoleKey.PageDown;
-			case Key.Home:
-				return (uint)ConsoleKey.Home;
-			case Key.End:
-				return (uint)ConsoleKey.End;
-			case Key.InsertChar:
-				return (uint)ConsoleKey.Insert;
-			case Key.DeleteChar:
-				return (uint)ConsoleKey.Delete;
-			case Key.F1:
-				return (uint)ConsoleKey.F1;
-			case Key.F2:
-				return (uint)ConsoleKey.F2;
-			case Key.F3:
-				return (uint)ConsoleKey.F3;
-			case Key.F4:
-				return (uint)ConsoleKey.F4;
-			case Key.F5:
-				return (uint)ConsoleKey.F5;
-			case Key.F6:
-				return (uint)ConsoleKey.F6;
-			case Key.F7:
-				return (uint)ConsoleKey.F7;
-			case Key.F8:
-				return (uint)ConsoleKey.F8;
-			case Key.F9:
-				return (uint)ConsoleKey.F9;
-			case Key.F10:
-				return (uint)ConsoleKey.F10;
-			case Key.F11:
-				return (uint)ConsoleKey.F11;
-			case Key.F12:
-				return (uint)ConsoleKey.F12;
-			case Key.F13:
-				return (uint)ConsoleKey.F13;
-			case Key.F14:
-				return (uint)ConsoleKey.F14;
-			case Key.F15:
-				return (uint)ConsoleKey.F15;
-			case Key.F16:
-				return (uint)ConsoleKey.F16;
-			case Key.F17:
-				return (uint)ConsoleKey.F17;
-			case Key.F18:
-				return (uint)ConsoleKey.F18;
-			case Key.F19:
-				return (uint)ConsoleKey.F19;
-			case Key.F20:
-				return (uint)ConsoleKey.F20;
-			case Key.F21:
-				return (uint)ConsoleKey.F21;
-			case Key.F22:
-				return (uint)ConsoleKey.F22;
-			case Key.F23:
-				return (uint)ConsoleKey.F23;
-			case Key.F24:
-				return (uint)ConsoleKey.F24;
-			case Key.BackTab:
-				return (uint)ConsoleKey.Tab;
-			case Key.Unknown:
-				isMappable = true;
-				return 0;
-			}
-			isMappable = true;
-
-			return keyValue;
-		}
-
-		/// <summary>
-		/// Maps a <see cref="ConsoleKey"/> to a <see cref="Key"/>.
-		/// </summary>
-		/// <param name="consoleKey">The console key.</param>
-		/// <param name="isMappable">If <see langword="true"/> is mapped to a valid character, otherwise <see langword="false"/>.</param>
-		/// <returns>The <see cref="Key"/> or the <paramref name="consoleKey"/>.</returns>
-		public static Key MapConsoleKeyToKey (ConsoleKey consoleKey, out bool isMappable)
-		{
-			isMappable = false;
-
-			switch (consoleKey) {
-			case ConsoleKey.Delete:
-				return Key.Delete;
-			case ConsoleKey.UpArrow:
-				return Key.CursorUp;
-			case ConsoleKey.DownArrow:
-				return Key.CursorDown;
-			case ConsoleKey.LeftArrow:
-				return Key.CursorLeft;
-			case ConsoleKey.RightArrow:
-				return Key.CursorRight;
-			case ConsoleKey.PageUp:
-				return Key.PageUp;
-			case ConsoleKey.PageDown:
-				return Key.PageDown;
-			case ConsoleKey.Home:
-				return Key.Home;
-			case ConsoleKey.End:
-				return Key.End;
-			case ConsoleKey.Insert:
-				return Key.InsertChar;
-			case ConsoleKey.F1:
-				return Key.F1;
-			case ConsoleKey.F2:
-				return Key.F2;
-			case ConsoleKey.F3:
-				return Key.F3;
-			case ConsoleKey.F4:
-				return Key.F4;
-			case ConsoleKey.F5:
-				return Key.F5;
-			case ConsoleKey.F6:
-				return Key.F6;
-			case ConsoleKey.F7:
-				return Key.F7;
-			case ConsoleKey.F8:
-				return Key.F8;
-			case ConsoleKey.F9:
-				return Key.F9;
-			case ConsoleKey.F10:
-				return Key.F10;
-			case ConsoleKey.F11:
-				return Key.F11;
-			case ConsoleKey.F12:
-				return Key.F12;
-			case ConsoleKey.F13:
-				return Key.F13;
-			case ConsoleKey.F14:
-				return Key.F14;
-			case ConsoleKey.F15:
-				return Key.F15;
-			case ConsoleKey.F16:
-				return Key.F16;
-			case ConsoleKey.F17:
-				return Key.F17;
-			case ConsoleKey.F18:
-				return Key.F18;
-			case ConsoleKey.F19:
-				return Key.F19;
-			case ConsoleKey.F20:
-				return Key.F20;
-			case ConsoleKey.F21:
-				return Key.F21;
-			case ConsoleKey.F22:
-				return Key.F22;
-			case ConsoleKey.F23:
-				return Key.F23;
-			case ConsoleKey.F24:
-				return Key.F24;
-			case ConsoleKey.Tab:
-				return Key.BackTab;
-			}
-			isMappable = true;
-
-			return (Key)consoleKey;
-		}
-
-		/// <summary>
-		/// Maps a <see cref="ConsoleKeyInfo"/> to a <see cref="Key"/>.
-		/// </summary>
-		/// <param name="keyInfo">The console key info.</param>
-		/// <param name="key">The key.</param>
-		/// <returns>The <see cref="Key"/> with <see cref="ConsoleModifiers"/> or the <paramref name="key"/></returns>
-		public static Key MapKeyModifiers (ConsoleKeyInfo keyInfo, Key key)
-		{
-			Key keyMod = new Key ();
-			if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0)
-				keyMod = Key.ShiftMask;
-			if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0)
-				keyMod |= Key.CtrlMask;
-			if ((keyInfo.Modifiers & ConsoleModifiers.Alt) != 0)
-				keyMod |= Key.AltMask;
-
-			return keyMod != Key.Null ? keyMod | key : key;
-		}
-
-		private static HashSet<ScanCodeMapping> scanCodes = new HashSet<ScanCodeMapping> {
-			new ScanCodeMapping (1,27,0,27),	// Escape
-			new ScanCodeMapping (1,27,ConsoleModifiers.Shift,27),
-			new ScanCodeMapping (2,49,0,49),	// D1
-			new ScanCodeMapping (2,49,ConsoleModifiers.Shift,33),
-			new ScanCodeMapping (3,50,0,50),	// D2
-			new ScanCodeMapping (3,50,ConsoleModifiers.Shift,34),
-			new ScanCodeMapping (3,50,ConsoleModifiers.Alt | ConsoleModifiers.Control,64),
-			new ScanCodeMapping (4,51,0,51),	// D3
-			new ScanCodeMapping (4,51,ConsoleModifiers.Shift,35),
-			new ScanCodeMapping (4,51,ConsoleModifiers.Alt | ConsoleModifiers.Control,163),
-			new ScanCodeMapping (5,52,0,52),	// D4
-			new ScanCodeMapping (5,52,ConsoleModifiers.Shift,36),
-			new ScanCodeMapping (5,52,ConsoleModifiers.Alt | ConsoleModifiers.Control,167),
-			new ScanCodeMapping (6,53,0,53),	// D5
-			new ScanCodeMapping (6,53,ConsoleModifiers.Shift,37),
-			new ScanCodeMapping (6,53,ConsoleModifiers.Alt | ConsoleModifiers.Control,8364),
-			new ScanCodeMapping (7,54,0,54),	// D6
-			new ScanCodeMapping (7,54,ConsoleModifiers.Shift,38),
-			new ScanCodeMapping (8,55,0,55),	// D7
-			new ScanCodeMapping (8,55,ConsoleModifiers.Shift,47),
-			new ScanCodeMapping (8,55,ConsoleModifiers.Alt | ConsoleModifiers.Control,123),
-			new ScanCodeMapping (9,56,0,56),	// D8
-			new ScanCodeMapping (9,56,ConsoleModifiers.Shift,40),
-			new ScanCodeMapping (9,56,ConsoleModifiers.Alt | ConsoleModifiers.Control,91),
-			new ScanCodeMapping (10,57,0,57),	// D9
-			new ScanCodeMapping (10,57,ConsoleModifiers.Shift,41),
-			new ScanCodeMapping (10,57,ConsoleModifiers.Alt | ConsoleModifiers.Control,93),
-			new ScanCodeMapping (11,48,0,48),	// D0
-			new ScanCodeMapping (11,48,ConsoleModifiers.Shift,61),
-			new ScanCodeMapping (11,48,ConsoleModifiers.Alt | ConsoleModifiers.Control,125),
-			new ScanCodeMapping (12,219,0,39),	// Oem4
-			new ScanCodeMapping (12,219,ConsoleModifiers.Shift,63),
-			new ScanCodeMapping (13,221,0,171),	// Oem6
-			new ScanCodeMapping (13,221,ConsoleModifiers.Shift,187),
-			new ScanCodeMapping (14,8,0,8),		// Backspace
-			new ScanCodeMapping (14,8,ConsoleModifiers.Shift,8),
-			new ScanCodeMapping (15,9,0,9),		// Tab
-			new ScanCodeMapping (15,9,ConsoleModifiers.Shift,15),
-			new ScanCodeMapping (16,81,0,113),	// Q
-			new ScanCodeMapping (16,81,ConsoleModifiers.Shift,81),
-			new ScanCodeMapping (17,87,0,119),	// W
-			new ScanCodeMapping (17,87,ConsoleModifiers.Shift,87),
-			new ScanCodeMapping (18,69,0,101),	// E
-			new ScanCodeMapping (18,69,ConsoleModifiers.Shift,69),
-			new ScanCodeMapping (19,82,0,114),	// R
-			new ScanCodeMapping (19,82,ConsoleModifiers.Shift,82),
-			new ScanCodeMapping (20,84,0,116),	// T
-			new ScanCodeMapping (20,84,ConsoleModifiers.Shift,84),
-			new ScanCodeMapping (21,89,0,121),	// Y
-			new ScanCodeMapping (21,89,ConsoleModifiers.Shift,89),
-			new ScanCodeMapping (22,85,0,117),	// U
-			new ScanCodeMapping (22,85,ConsoleModifiers.Shift,85),
-			new ScanCodeMapping (23,73,0,105),	// I
-			new ScanCodeMapping (23,73,ConsoleModifiers.Shift,73),
-			new ScanCodeMapping (24,79,0,111),	// O
-			new ScanCodeMapping (24,79,ConsoleModifiers.Shift,79),
-			new ScanCodeMapping (25,80,0,112),	// P
-			new ScanCodeMapping (25,80,ConsoleModifiers.Shift,80),
-			new ScanCodeMapping (26,187,0,43),	// OemPlus
-			new ScanCodeMapping (26,187,ConsoleModifiers.Shift,42),
-			new ScanCodeMapping (26,187,ConsoleModifiers.Alt | ConsoleModifiers.Control,168),
-			new ScanCodeMapping (27,186,0,180),	// Oem1
-			new ScanCodeMapping (27,186,ConsoleModifiers.Shift,96),
-			new ScanCodeMapping (28,13,0,13),	// Enter
-			new ScanCodeMapping (28,13,ConsoleModifiers.Shift,13),
-			new ScanCodeMapping (29,17,0,0),	// Control
-			new ScanCodeMapping (29,17,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (30,65,0,97),	// A
-			new ScanCodeMapping (30,65,ConsoleModifiers.Shift,65),
-			new ScanCodeMapping (31,83,0,115),	// S
-			new ScanCodeMapping (31,83,ConsoleModifiers.Shift,83),
-			new ScanCodeMapping (32,68,0,100),	// D
-			new ScanCodeMapping (32,68,ConsoleModifiers.Shift,68),
-			new ScanCodeMapping (33,70,0,102),	// F
-			new ScanCodeMapping (33,70,ConsoleModifiers.Shift,70),
-			new ScanCodeMapping (34,71,0,103),	// G
-			new ScanCodeMapping (34,71,ConsoleModifiers.Shift,71),
-			new ScanCodeMapping (35,72,0,104),	// H
-			new ScanCodeMapping (35,72,ConsoleModifiers.Shift,72),
-			new ScanCodeMapping (36,74,0,106),	// J
-			new ScanCodeMapping (36,74,ConsoleModifiers.Shift,74),
-			new ScanCodeMapping (37,75,0,107),	// K
-			new ScanCodeMapping (37,75,ConsoleModifiers.Shift,75),
-			new ScanCodeMapping (38,76,0,108),	// L
-			new ScanCodeMapping (38,76,ConsoleModifiers.Shift,76),
-			new ScanCodeMapping (39,192,0,231),	// Oem3
-			new ScanCodeMapping (39,192,ConsoleModifiers.Shift,199),
-			new ScanCodeMapping (40,222,0,186),	// Oem7
-			new ScanCodeMapping (40,222,ConsoleModifiers.Shift,170),
-			new ScanCodeMapping (41,220,0,92),	// Oem5
-			new ScanCodeMapping (41,220,ConsoleModifiers.Shift,124),
-			new ScanCodeMapping (42,16,0,0),	// LShift
-			new ScanCodeMapping (42,16,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (43,191,0,126),	// Oem2
-			new ScanCodeMapping (43,191,ConsoleModifiers.Shift,94),
-			new ScanCodeMapping (44,90,0,122),	// Z
-			new ScanCodeMapping (44,90,ConsoleModifiers.Shift,90),
-			new ScanCodeMapping (45,88,0,120),	// X
-			new ScanCodeMapping (45,88,ConsoleModifiers.Shift,88),
-			new ScanCodeMapping (46,67,0,99),	// C
-			new ScanCodeMapping (46,67,ConsoleModifiers.Shift,67),
-			new ScanCodeMapping (47,86,0,118),	// V
-			new ScanCodeMapping (47,86,ConsoleModifiers.Shift,86),
-			new ScanCodeMapping (48,66,0,98),	// B
-			new ScanCodeMapping (48,66,ConsoleModifiers.Shift,66),
-			new ScanCodeMapping (49,78,0,110),	// N
-			new ScanCodeMapping (49,78,ConsoleModifiers.Shift,78),
-			new ScanCodeMapping (50,77,0,109),	// M
-			new ScanCodeMapping (50,77,ConsoleModifiers.Shift,77),
-			new ScanCodeMapping (51,188,0,44),	// OemComma
-			new ScanCodeMapping (51,188,ConsoleModifiers.Shift,59),
-			new ScanCodeMapping (52,190,0,46),	// OemPeriod
-			new ScanCodeMapping (52,190,ConsoleModifiers.Shift,58),
-			new ScanCodeMapping (53,189,0,45),	// OemMinus
-			new ScanCodeMapping (53,189,ConsoleModifiers.Shift,95),
-			new ScanCodeMapping (54,16,0,0),	// RShift
-			new ScanCodeMapping (54,16,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (55,44,0,0),	// PrintScreen
-			new ScanCodeMapping (55,44,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (56,18,0,0),	// Alt
-			new ScanCodeMapping (56,18,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (57,32,0,32),	// Spacebar
-			new ScanCodeMapping (57,32,ConsoleModifiers.Shift,32),
-			new ScanCodeMapping (58,20,0,0),	// Caps
-			new ScanCodeMapping (58,20,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (59,112,0,0),	// F1
-			new ScanCodeMapping (59,112,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (60,113,0,0),	// F2
-			new ScanCodeMapping (60,113,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (61,114,0,0),	// F3
-			new ScanCodeMapping (61,114,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (62,115,0,0),	// F4
-			new ScanCodeMapping (62,115,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (63,116,0,0),	// F5
-			new ScanCodeMapping (63,116,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (64,117,0,0),	// F6
-			new ScanCodeMapping (64,117,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (65,118,0,0),	// F7
-			new ScanCodeMapping (65,118,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (66,119,0,0),	// F8
-			new ScanCodeMapping (66,119,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (67,120,0,0),	// F9
-			new ScanCodeMapping (67,120,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (68,121,0,0),	// F10
-			new ScanCodeMapping (68,121,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (69,144,0,0),	// Num
-			new ScanCodeMapping (69,144,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (70,145,0,0),	// Scroll
-			new ScanCodeMapping (70,145,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (71,36,0,0),	// Home
-			new ScanCodeMapping (71,36,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (72,38,0,0),	// UpArrow
-			new ScanCodeMapping (72,38,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (73,33,0,0),	// PageUp
-			new ScanCodeMapping (73,33,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (74,109,0,45),	// Subtract
-			new ScanCodeMapping (74,109,ConsoleModifiers.Shift,45),
-			new ScanCodeMapping (75,37,0,0),	// LeftArrow
-			new ScanCodeMapping (75,37,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (76,12,0,0),	// Center
-			new ScanCodeMapping (76,12,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (77,39,0,0),	// RightArrow
-			new ScanCodeMapping (77,39,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (78,107,0,43),	// Add
-			new ScanCodeMapping (78,107,ConsoleModifiers.Shift,43),
-			new ScanCodeMapping (79,35,0,0),	// End
-			new ScanCodeMapping (79,35,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (80,40,0,0),	// DownArrow
-			new ScanCodeMapping (80,40,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (81,34,0,0),	// PageDown
-			new ScanCodeMapping (81,34,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (82,45,0,0),	// Insert
-			new ScanCodeMapping (82,45,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (83,46,0,0),	// Delete
-			new ScanCodeMapping (83,46,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (86,226,0,60),	// OEM 102
-			new ScanCodeMapping (86,226,ConsoleModifiers.Shift,62),
-			new ScanCodeMapping (87,122,0,0),	// F11
-			new ScanCodeMapping (87,122,ConsoleModifiers.Shift,0),
-			new ScanCodeMapping (88,123,0,0),	// F12
-			new ScanCodeMapping (88,123,ConsoleModifiers.Shift,0)
-		};
-	}
-}

+ 0 - 837
Terminal.Gui/Input/Event.cs

@@ -1,837 +0,0 @@
-//
-// Evemts.cs: Events, Key mappings
-//
-// Authors:
-//   Miguel de Icaza ([email protected])
-//
-using System;
-
-namespace Terminal.Gui {
-
-	/// <summary>
-	/// Identifies the state of the "shift"-keys within a event.
-	/// </summary>
-	public class KeyModifiers {
-		/// <summary>
-		/// Check if the Shift key was pressed or not.
-		/// </summary>
-		public bool Shift;
-		/// <summary>
-		/// Check if the Alt key was pressed or not.
-		/// </summary>
-		public bool Alt;
-		/// <summary>
-		/// Check if the Ctrl key was pressed or not.
-		/// </summary>
-		public bool Ctrl;
-		/// <summary>
-		/// Check if the Caps lock key was pressed or not.
-		/// </summary>
-		public bool Capslock;
-		/// <summary>
-		/// Check if the Num lock key was pressed or not.
-		/// </summary>
-		public bool Numlock;
-		/// <summary>
-		/// Check if the Scroll lock key was pressed or not.
-		/// </summary>
-		public bool Scrolllock;
-	}
-
-	/// <summary>
-	/// The <see cref="Key"/> enumeration contains special encoding for some keys, but can also
-	/// encode all the unicode values that can be passed.   
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	///   If the <see cref="SpecialMask"/> is set, then the value is that of the special mask,
-	///   otherwise, the value is the one of the lower bits (as extracted by <see cref="CharMask"/>)
-	/// <para>
-	///   Numerics keys are the values between 48 and 57 corresponding to 0 to 9
-	/// </para>
-	/// </para>
-	/// <para>
-	///   Upper alpha keys are the values between 65 and 90 corresponding to A to Z
-	/// </para>
-	/// <para>
-	///   Unicode runes are also stored here, the letter 'A" for example is encoded as a value 65 (not surfaced in the enum).
-	/// </para>
-	/// </remarks>
-	[Flags]
-	public enum Key : uint {
-		/// <summary>
-		/// Mask that indicates that this is a character value, values outside this range
-		/// indicate special characters like Alt-key combinations or special keys on the
-		/// keyboard like function keys, arrows keys and so on.
-		/// </summary>
-		CharMask = 0xfffff,
-
-		/// <summary>
-		/// If the <see cref="SpecialMask"/> is set, then the value is that of the special mask,
-		/// otherwise, the value is the one of the lower bits (as extracted by <see cref="CharMask"/>).
-		/// </summary>
-		SpecialMask = 0xfff00000,
-
-		/// <summary>
-		/// The key code representing null or empty
-		/// </summary>
-		Null = '\0',
-
-		/// <summary>
-		/// Backspace key.
-		/// </summary>
-		Backspace = 8,
-
-		/// <summary>
-		/// The key code for the user pressing the tab key (forwards tab key).
-		/// </summary>
-		Tab = 9,
-
-		/// <summary>
-		/// The key code for the user pressing the return key.
-		/// </summary>
-		Enter = '\n',
-
-		/// <summary>
-		/// The key code for the user pressing the clear key.
-		/// </summary>
-		Clear = 12,
-
-		/// <summary>
-		/// The key code for the user pressing the escape key
-		/// </summary>
-		Esc = 27,
-
-		/// <summary>
-		/// The key code for the user pressing the space bar
-		/// </summary>
-		Space = 32,
-
-		/// <summary>
-		/// Digit 0.
-		/// </summary>
-		D0 = 48,
-		/// <summary>
-		/// Digit 1.
-		/// </summary>
-		D1,
-		/// <summary>
-		/// Digit 2.
-		/// </summary>
-		D2,
-		/// <summary>
-		/// Digit 3.
-		/// </summary>
-		D3,
-		/// <summary>
-		/// Digit 4.
-		/// </summary>
-		D4,
-		/// <summary>
-		/// Digit 5.
-		/// </summary>
-		D5,
-		/// <summary>
-		/// Digit 6.
-		/// </summary>
-		D6,
-		/// <summary>
-		/// Digit 7.
-		/// </summary>
-		D7,
-		/// <summary>
-		/// Digit 8.
-		/// </summary>
-		D8,
-		/// <summary>
-		/// Digit 9.
-		/// </summary>
-		D9,
-
-		/// <summary>
-		/// The key code for the user pressing Shift-A
-		/// </summary>
-		A = 65,
-		/// <summary>
-		/// The key code for the user pressing Shift-B
-		/// </summary>
-		B,
-		/// <summary>
-		/// The key code for the user pressing Shift-C
-		/// </summary>
-		C,
-		/// <summary>
-		/// The key code for the user pressing Shift-D
-		/// </summary>
-		D,
-		/// <summary>
-		/// The key code for the user pressing Shift-E
-		/// </summary>
-		E,
-		/// <summary>
-		/// The key code for the user pressing Shift-F
-		/// </summary>
-		F,
-		/// <summary>
-		/// The key code for the user pressing Shift-G
-		/// </summary>
-		G,
-		/// <summary>
-		/// The key code for the user pressing Shift-H
-		/// </summary>
-		H,
-		/// <summary>
-		/// The key code for the user pressing Shift-I
-		/// </summary>
-		I,
-		/// <summary>
-		/// The key code for the user pressing Shift-J
-		/// </summary>
-		J,
-		/// <summary>
-		/// The key code for the user pressing Shift-K
-		/// </summary>
-		K,
-		/// <summary>
-		/// The key code for the user pressing Shift-L
-		/// </summary>
-		L,
-		/// <summary>
-		/// The key code for the user pressing Shift-M
-		/// </summary>
-		M,
-		/// <summary>
-		/// The key code for the user pressing Shift-N
-		/// </summary>
-		N,
-		/// <summary>
-		/// The key code for the user pressing Shift-O
-		/// </summary>
-		O,
-		/// <summary>
-		/// The key code for the user pressing Shift-P
-		/// </summary>
-		P,
-		/// <summary>
-		/// The key code for the user pressing Shift-Q
-		/// </summary>
-		Q,
-		/// <summary>
-		/// The key code for the user pressing Shift-R
-		/// </summary>
-		R,
-		/// <summary>
-		/// The key code for the user pressing Shift-S
-		/// </summary>
-		S,
-		/// <summary>
-		/// The key code for the user pressing Shift-T
-		/// </summary>
-		T,
-		/// <summary>
-		/// The key code for the user pressing Shift-U
-		/// </summary>
-		U,
-		/// <summary>
-		/// The key code for the user pressing Shift-V
-		/// </summary>
-		V,
-		/// <summary>
-		/// The key code for the user pressing Shift-W
-		/// </summary>
-		W,
-		/// <summary>
-		/// The key code for the user pressing Shift-X
-		/// </summary>
-		X,
-		/// <summary>
-		/// The key code for the user pressing Shift-Y
-		/// </summary>
-		Y,
-		/// <summary>
-		/// The key code for the user pressing Shift-Z
-		/// </summary>
-		Z,
-		/// <summary>
-		/// The key code for the user pressing A
-		/// </summary>
-		a = 97,
-		/// <summary>
-		/// The key code for the user pressing B
-		/// </summary>
-		b,
-		/// <summary>
-		/// The key code for the user pressing C
-		/// </summary>
-		c,
-		/// <summary>
-		/// The key code for the user pressing D
-		/// </summary>
-		d,
-		/// <summary>
-		/// The key code for the user pressing E
-		/// </summary>
-		e,
-		/// <summary>
-		/// The key code for the user pressing F
-		/// </summary>
-		f,
-		/// <summary>
-		/// The key code for the user pressing G
-		/// </summary>
-		g,
-		/// <summary>
-		/// The key code for the user pressing H
-		/// </summary>
-		h,
-		/// <summary>
-		/// The key code for the user pressing I
-		/// </summary>
-		i,
-		/// <summary>
-		/// The key code for the user pressing J
-		/// </summary>
-		j,
-		/// <summary>
-		/// The key code for the user pressing K
-		/// </summary>
-		k,
-		/// <summary>
-		/// The key code for the user pressing L
-		/// </summary>
-		l,
-		/// <summary>
-		/// The key code for the user pressing M
-		/// </summary>
-		m,
-		/// <summary>
-		/// The key code for the user pressing N
-		/// </summary>
-		n,
-		/// <summary>
-		/// The key code for the user pressing O
-		/// </summary>
-		o,
-		/// <summary>
-		/// The key code for the user pressing P
-		/// </summary>
-		p,
-		/// <summary>
-		/// The key code for the user pressing Q
-		/// </summary>
-		q,
-		/// <summary>
-		/// The key code for the user pressing R
-		/// </summary>
-		r,
-		/// <summary>
-		/// The key code for the user pressing S
-		/// </summary>
-		s,
-		/// <summary>
-		/// The key code for the user pressing T
-		/// </summary>
-		t,
-		/// <summary>
-		/// The key code for the user pressing U
-		/// </summary>
-		u,
-		/// <summary>
-		/// The key code for the user pressing V
-		/// </summary>
-		v,
-		/// <summary>
-		/// The key code for the user pressing W
-		/// </summary>
-		w,
-		/// <summary>
-		/// The key code for the user pressing X
-		/// </summary>
-		x,
-		/// <summary>
-		/// The key code for the user pressing Y
-		/// </summary>
-		y,
-		/// <summary>
-		/// The key code for the user pressing Z
-		/// </summary>
-		z,
-		/// <summary>
-		/// The key code for the user pressing the delete key.
-		/// </summary>
-		Delete = 127,
-
-		/// <summary>
-		/// When this value is set, the Key encodes the sequence Shift-KeyValue.
-		/// </summary>
-		ShiftMask = 0x10000000,
-
-		/// <summary>
-		///   When this value is set, the Key encodes the sequence Alt-KeyValue.
-		///   And the actual value must be extracted by removing the AltMask.
-		/// </summary>
-		AltMask = 0x80000000,
-
-		/// <summary>
-		///   When this value is set, the Key encodes the sequence Ctrl-KeyValue.
-		///   And the actual value must be extracted by removing the CtrlMask.
-		/// </summary>
-		CtrlMask = 0x40000000,
-
-		/// <summary>
-		/// Cursor up key
-		/// </summary>
-		CursorUp = 0x100000,
-		/// <summary>
-		/// Cursor down key.
-		/// </summary>
-		CursorDown,
-		/// <summary>
-		/// Cursor left key.
-		/// </summary>
-		CursorLeft,
-		/// <summary>
-		/// Cursor right key.
-		/// </summary>
-		CursorRight,
-		/// <summary>
-		/// Page Up key.
-		/// </summary>
-		PageUp,
-		/// <summary>
-		/// Page Down key.
-		/// </summary>
-		PageDown,
-		/// <summary>
-		/// Home key.
-		/// </summary>
-		Home,
-		/// <summary>
-		/// End key.
-		/// </summary>
-		End,
-
-		/// <summary>
-		/// Insert character key.
-		/// </summary>
-		InsertChar,
-
-		/// <summary>
-		/// Delete character key.
-		/// </summary>
-		DeleteChar,
-
-		/// <summary>
-		/// Shift-tab key (backwards tab key).
-		/// </summary>
-		BackTab,
-
-		/// <summary>
-		/// Print screen character key.
-		/// </summary>
-		PrintScreen,
-
-		/// <summary>
-		/// F1 key.
-		/// </summary>
-		F1,
-		/// <summary>
-		/// F2 key.
-		/// </summary>
-		F2,
-		/// <summary>
-		/// F3 key.
-		/// </summary>
-		F3,
-		/// <summary>
-		/// F4 key.
-		/// </summary>
-		F4,
-		/// <summary>
-		/// F5 key.
-		/// </summary>
-		F5,
-		/// <summary>
-		/// F6 key.
-		/// </summary>
-		F6,
-		/// <summary>
-		/// F7 key.
-		/// </summary>
-		F7,
-		/// <summary>
-		/// F8 key.
-		/// </summary>
-		F8,
-		/// <summary>
-		/// F9 key.
-		/// </summary>
-		F9,
-		/// <summary>
-		/// F10 key.
-		/// </summary>
-		F10,
-		/// <summary>
-		/// F11 key.
-		/// </summary>
-		F11,
-		/// <summary>
-		/// F12 key.
-		/// </summary>
-		F12,
-		/// <summary>
-		/// F13 key.
-		/// </summary>
-		F13,
-		/// <summary>
-		/// F14 key.
-		/// </summary>
-		F14,
-		/// <summary>
-		/// F15 key.
-		/// </summary>
-		F15,
-		/// <summary>
-		/// F16 key.
-		/// </summary>
-		F16,
-		/// <summary>
-		/// F17 key.
-		/// </summary>
-		F17,
-		/// <summary>
-		/// F18 key.
-		/// </summary>
-		F18,
-		/// <summary>
-		/// F19 key.
-		/// </summary>
-		F19,
-		/// <summary>
-		/// F20 key.
-		/// </summary>
-		F20,
-		/// <summary>
-		/// F21 key.
-		/// </summary>
-		F21,
-		/// <summary>
-		/// F22 key.
-		/// </summary>
-		F22,
-		/// <summary>
-		/// F23 key.
-		/// </summary>
-		F23,
-		/// <summary>
-		/// F24 key.
-		/// </summary>
-		F24,
-
-		/// <summary>
-		/// A key with an unknown mapping was raised.
-		/// </summary>
-		Unknown
-	}
-
-	/// <summary>
-	/// Describes a keyboard event.
-	/// </summary>
-	public class KeyEvent {
-		KeyModifiers keyModifiers;
-
-		/// <summary>
-		/// Symbolic definition for the key.
-		/// </summary>
-		public Key Key;
-
-		/// <summary>
-		///   The key value cast to an integer, you will typical use this for
-		///   extracting the Unicode rune value out of a key, when none of the
-		///   symbolic options are in use.
-		/// </summary>
-		public int KeyValue => (int)Key;
-
-		/// <summary>
-		/// Gets a value indicating whether the Shift key was pressed.
-		/// </summary>
-		/// <value><c>true</c> if is shift; otherwise, <c>false</c>.</value>
-		public bool IsShift => keyModifiers.Shift || Key == Key.BackTab;
-
-		/// <summary>
-		/// Gets a value indicating whether the Alt key was pressed (real or synthesized)
-		/// </summary>
-		/// <value><c>true</c> if is alternate; otherwise, <c>false</c>.</value>
-		public bool IsAlt => keyModifiers.Alt;
-
-		/// <summary>
-		/// Determines whether the value is a control key (and NOT just the ctrl key)
-		/// </summary>
-		/// <value><c>true</c> if is ctrl; otherwise, <c>false</c>.</value>
-		//public bool IsCtrl => ((uint)Key >= 1) && ((uint)Key <= 26);
-		public bool IsCtrl => keyModifiers.Ctrl;
-
-		/// <summary>
-		/// Gets a value indicating whether the Caps lock key was pressed (real or synthesized)
-		/// </summary>
-		/// <value><c>true</c> if is alternate; otherwise, <c>false</c>.</value>
-		public bool IsCapslock => keyModifiers.Capslock;
-
-		/// <summary>
-		/// Gets a value indicating whether the Num lock key was pressed (real or synthesized)
-		/// </summary>
-		/// <value><c>true</c> if is alternate; otherwise, <c>false</c>.</value>
-		public bool IsNumlock => keyModifiers.Numlock;
-
-		/// <summary>
-		/// Gets a value indicating whether the Scroll lock key was pressed (real or synthesized)
-		/// </summary>
-		/// <value><c>true</c> if is alternate; otherwise, <c>false</c>.</value>
-		public bool IsScrolllock => keyModifiers.Scrolllock;
-
-		/// <summary>
-		/// Constructs a new <see cref="KeyEvent"/>
-		/// </summary>
-		public KeyEvent ()
-		{
-			Key = Key.Unknown;
-			keyModifiers = new KeyModifiers ();
-		}
-
-		/// <summary>
-		///   Constructs a new <see cref="KeyEvent"/> from the provided Key value - can be a rune cast into a Key value
-		/// </summary>
-		public KeyEvent (Key k, KeyModifiers km)
-		{
-			Key = k;
-			keyModifiers = km;
-		}
-
-		/// <summary>
-		/// Pretty prints the KeyEvent
-		/// </summary>
-		/// <returns></returns>
-		public override string ToString ()
-		{
-			string msg = "";
-			var key = this.Key;
-			if (keyModifiers.Shift) {
-				msg += "Shift-";
-			}
-			if (keyModifiers.Alt) {
-				msg += "Alt-";
-			}
-			if (keyModifiers.Ctrl) {
-				msg += "Ctrl-";
-			}
-			if (keyModifiers.Capslock) {
-				msg += "Capslock-";
-			}
-			if (keyModifiers.Numlock) {
-				msg += "Numlock-";
-			}
-			if (keyModifiers.Scrolllock) {
-				msg += "Scrolllock-";
-			}
-
-			msg += $"{((Key)KeyValue != Key.Unknown && ((uint)this.KeyValue & (uint)Key.CharMask) > 27 ? $"{(char)this.KeyValue}" : $"{key}")}";
-
-			return msg;
-		}
-	}
-
-	/// <summary>
-	/// Mouse flags reported in <see cref="MouseEvent"/>.
-	/// </summary>
-	/// <remarks>
-	/// They just happen to map to the ncurses ones.
-	/// </remarks>
-	[Flags]
-	public enum MouseFlags {
-		/// <summary>
-		/// The first mouse button was pressed.
-		/// </summary>
-		Button1Pressed = unchecked((int)0x2),
-		/// <summary>
-		/// The first mouse button was released.
-		/// </summary>
-		Button1Released = unchecked((int)0x1),
-		/// <summary>
-		/// The first mouse button was clicked (press+release).
-		/// </summary>
-		Button1Clicked = unchecked((int)0x4),
-		/// <summary>
-		/// The first mouse button was double-clicked.
-		/// </summary>
-		Button1DoubleClicked = unchecked((int)0x8),
-		/// <summary>
-		/// The first mouse button was triple-clicked.
-		/// </summary>
-		Button1TripleClicked = unchecked((int)0x10),
-		/// <summary>
-		/// The second mouse button was pressed.
-		/// </summary>
-		Button2Pressed = unchecked((int)0x80),
-		/// <summary>
-		/// The second mouse button was released.
-		/// </summary>
-		Button2Released = unchecked((int)0x40),
-		/// <summary>
-		/// The second mouse button was clicked (press+release).
-		/// </summary>
-		Button2Clicked = unchecked((int)0x100),
-		/// <summary>
-		/// The second mouse button was double-clicked.
-		/// </summary>
-		Button2DoubleClicked = unchecked((int)0x200),
-		/// <summary>
-		/// The second mouse button was triple-clicked.
-		/// </summary>
-		Button2TripleClicked = unchecked((int)0x400),
-		/// <summary>
-		/// The third mouse button was pressed.
-		/// </summary>
-		Button3Pressed = unchecked((int)0x2000),
-		/// <summary>
-		/// The third mouse button was released.
-		/// </summary>
-		Button3Released = unchecked((int)0x1000),
-		/// <summary>
-		/// The third mouse button was clicked (press+release).
-		/// </summary>
-		Button3Clicked = unchecked((int)0x4000),
-		/// <summary>
-		/// The third mouse button was double-clicked.
-		/// </summary>
-		Button3DoubleClicked = unchecked((int)0x8000),
-		/// <summary>
-		/// The third mouse button was triple-clicked.
-		/// </summary>
-		Button3TripleClicked = unchecked((int)0x10000),
-		/// <summary>
-		/// The fourth mouse button was pressed.
-		/// </summary>
-		Button4Pressed = unchecked((int)0x80000),
-		/// <summary>
-		/// The fourth mouse button was released.
-		/// </summary>
-		Button4Released = unchecked((int)0x40000),
-		/// <summary>
-		/// The fourth button was clicked (press+release).
-		/// </summary>
-		Button4Clicked = unchecked((int)0x100000),
-		/// <summary>
-		/// The fourth button was double-clicked.
-		/// </summary>
-		Button4DoubleClicked = unchecked((int)0x200000),
-		/// <summary>
-		/// The fourth button was triple-clicked.
-		/// </summary>
-		Button4TripleClicked = unchecked((int)0x400000),
-		/// <summary>
-		/// Flag: the shift key was pressed when the mouse button took place.
-		/// </summary>
-		ButtonShift = unchecked((int)0x2000000),
-		/// <summary>
-		/// Flag: the ctrl key was pressed when the mouse button took place.
-		/// </summary>
-		ButtonCtrl = unchecked((int)0x1000000),
-		/// <summary>
-		/// Flag: the alt key was pressed when the mouse button took place.
-		/// </summary>
-		ButtonAlt = unchecked((int)0x4000000),
-		/// <summary>
-		/// The mouse position is being reported in this event.
-		/// </summary>
-		ReportMousePosition = unchecked((int)0x8000000),
-		/// <summary>
-		/// Vertical button wheeled up.
-		/// </summary>
-		WheeledUp = unchecked((int)0x10000000),
-		/// <summary>
-		/// Vertical button wheeled down.
-		/// </summary>
-		WheeledDown = unchecked((int)0x20000000),
-		/// <summary>
-		/// Vertical button wheeled up while pressing ButtonShift.
-		/// </summary>
-		WheeledLeft = ButtonShift | WheeledUp,
-		/// <summary>
-		/// Vertical button wheeled down while pressing ButtonShift.
-		/// </summary>
-		WheeledRight = ButtonShift | WheeledDown,
-		/// <summary>
-		/// Mask that captures all the events.
-		/// </summary>
-		AllEvents = unchecked((int)0x7ffffff),
-	}
-
-	// TODO: Merge MouseEvent and MouseEventEventArgs into a single class.
-
-	/// <summary>
-	/// Low-level construct that conveys the details of mouse events, such
-	/// as coordinates and button state, from ConsoleDrivers up to <see cref="Application"/> and then to
-	/// Views.
-	/// </summary>
-	/// <remarks>See <see cref="Application.OnMouseEvent(MouseEventEventArgs)"/> and <see cref="Responder.OnMouseEnter(MouseEvent)"/>.</remarks>
-	public class MouseEvent {
-		/// <summary>
-		/// The X (column) location for the mouse event relative to <see cref="View.Bounds"/>.
-		/// </summary>
-		public int X { get; set; }
-
-		/// <summary>
-		/// The Y (column) location for the mouse event relative to <see cref="View.Bounds"/>.
-		/// </summary>
-		public int Y { get; set; }
-
-		/// <summary>
-		/// Gets or sets the flags that indicate the kind of mouse event that is being posted.
-		/// </summary>
-		public MouseFlags Flags { get; set; }
-
-		/// <summary>
-		/// Provides the X (column) mouse position offset from the grabbed view (see <see cref="Application.GrabMouse"/>.
-		/// </summary>
-		/// <remarks>
-		/// Calculated and processed in <see cref="Application.OnMouseEvent(MouseEventEventArgs)"/>.
-		/// Whichever view that has called <see cref="Application.GrabMouse"/>, will receive all the mouse event
-		/// with <see cref="View.Bounds"/> relative coordinates. The <see cref="OfX"/> and <see cref="OfY"/> provide
-		/// the screen-relative offset of these coordinates.
-		/// Using these properties, the view that has grabbed the mouse will know how much the mouse has moved.
-		/// </remarks>
-		public int OfX { get; set; }
-
-		/// <summary>
-		/// Provides the Y (row) mouse position offset from the grabbed view (see <see cref="Application.GrabMouse"/>.
-		/// </summary>
-		/// <remarks>
-		/// Calculated and processed in <see cref="Application.OnMouseEvent(MouseEventEventArgs)"/>.
-		/// Whichever view that has called <see cref="Application.GrabMouse"/>, will receive all the mouse event
-		/// with <see cref="View.Bounds"/> relative coordinates. The <see cref="OfX"/> and <see cref="OfY"/> provide
-		/// the screen-relative offset of these coordinates.
-		/// Using these properties, the view that has grabbed the mouse will know how much the mouse has moved.
-		/// </remarks>
-		public int OfY { get; set; }
-
-		/// <summary>
-		/// Gets or sets the view that should process the mouse event.
-		/// </summary>
-		public View View { get; set; }
-
-		/// <summary>
-		/// Indicates if the mouse event has been handled by a view and other subscribers should ignore the event.
-		/// IMPORTANT: Set this value to <see langword="true"/> when updating any View's layout from inside the subscriber method.
-		/// </summary>
-		public bool Handled { get; set; }
-
-		/// <summary>
-		/// Returns a <see cref="T:System.String"/> that represents the current <see cref="MouseEvent"/>.
-		/// </summary>
-		/// <returns>A <see cref="T:System.String"/> that represents the current <see cref="MouseEvent"/>.</returns>
-		public override string ToString ()
-		{
-			return $"({X},{Y}:{Flags}";
-		}
-	}
-}

+ 976 - 0
Terminal.Gui/Input/Key.cs

@@ -0,0 +1,976 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Text;
+using System.Text.Json.Serialization;
+
+namespace Terminal.Gui;
+
+/// <summary>
+/// Provides an abstraction for common keyboard operations and state. Used for processing keyboard input and raising keyboard events.
+/// </summary>
+/// <remarks>
+/// <para>
+/// This class provides a high-level abstraction with helper methods and properties for common keyboard operations. Use this class
+/// instead of the <see cref="Terminal.Gui.KeyCode"/> enumeration for keyboard input whenever possible.
+/// </para>
+/// <para>
+/// 
+/// </para>
+/// <para>
+/// The default value for <see cref="Key"/> is <see cref="KeyCode.Null"/> and can be tested using <see cref="Key.Empty"/>.
+/// </para>
+/// <para>
+/// <list type="table">
+///	<listheader>
+///	<term>Concept</term><description>Definition</description>
+///	</listheader>
+///	<item>
+///	<term>Testing Shift State</term>
+///	<description>
+///	The <c>Is</c> properties (<see cref="IsShift"/>,<see cref="IsCtrl"/>, <see cref="IsAlt"/>) test for shift state; whether the key press was modified by a shift key.
+///	</description>
+///	</item>
+///	<item>
+/// 	<term>Adding Shift State</term>
+///	<description>
+///	The <c>With</c> properties (<see cref="WithShift"/>,<see cref="WithCtrl"/>, <see cref="WithAlt"/>) return a copy of the Key with the shift modifier applied. This
+///     is useful for specifying a key that requires a shift modifier (e.g. <c>var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;</c>.
+///	</description>
+///	</item>
+///	<item>
+/// 	<term>Removing Shift State</term>
+///	<description>
+///	The <c>No</c> properties (<see cref="NoShift"/>,<see cref="NoCtrl"/>, <see cref="NoAlt"/>) return a copy of the Key with the shift modifier removed. This
+///     is useful for specifying a key that does not require a shift modifier (e.g. <c>var ControlDelete = ControlAltDelete.NoCtrl;</c>.
+///	</description>
+///	</item>
+///	<item>
+///	<term>Encoding of A..Z</term>
+///	<description>
+///	Lowercase alpha keys are encoded (in <see cref="Key.KeyCode"/>) as values between 65 and 90 corresponding to
+///	the un-shifted A to Z keys on a keyboard. Properties are provided for these (e.g. <see cref="Key.A"/>, <see cref="Key.B"/>, etc.).
+///	Even though the encoded values are the same as the ASCII values for uppercase characters, these enum values represent *lowercase*, un-shifted characters.
+///	</description>
+///	</item>
+///	<item>
+///     <term>Persistence as strings</term>
+///	<description>
+///     Keys are persisted as <c>"[Modifiers]+[Key]</c>. For example <c>new Key(Key.Delete).WithAlt.WithDel</c> is persisted as <c>"Ctrl+Alt+Delete"</c>. See <see cref="ToString()"/>
+///     and <see cref="TryParse(string, out Terminal.Gui.Key)"/> for more information.
+///	</description>
+///	</item>
+/// </list>
+/// </para>
+/// </remarks>
+[JsonConverter (typeof (KeyJsonConverter))]
+public class Key : EventArgs, IEquatable<Key> {
+	/// <summary>
+	/// Constructs a new <see cref="Key"/>
+	/// </summary>
+	public Key () : this (KeyCode.Null) { }
+
+	/// <summary>
+	///   Constructs a new <see cref="Key"/> from the provided Key value
+	/// </summary>
+	/// <param name="k">The key</param>
+	public Key (KeyCode k) => KeyCode = k;
+
+	/// <summary>
+	/// Indicates if the current Key event has already been processed and the driver should stop notifying any other event subscriber.
+	/// Its important to set this value to true specially when updating any View's layout from inside the subscriber method.
+	/// </summary>
+	public bool Handled { get; set; } = false;
+
+	/// <summary>
+	/// The encoded key value. 
+	/// </summary>
+	/// <para>
+	/// IMPORTANT: Lowercase alpha keys are encoded (in <see cref="Gui.KeyCode"/>) as values between 65 and 90 corresponding to the un-shifted A to Z keys on a keyboard. Enum values
+	/// are provided for these (e.g. <see cref="KeyCode.A"/>, <see cref="KeyCode.B"/>, etc.). Even though the values are the same as the ASCII
+	/// values for uppercase characters, these enum values represent *lowercase*, un-shifted characters.
+	/// </para>
+	/// <remarks>
+	/// This property is the backing data for the <see cref="Key"/>. It is a <see cref="KeyCode"/> enum value.
+	/// </remarks>
+	[JsonInclude] [JsonConverter (typeof (KeyCodeJsonConverter))]
+	public KeyCode KeyCode { get; set; }
+
+	/// <summary>
+	/// Enables passing the key binding scope with the event. Default is <see cref="KeyBindingScope.Focused"/>.
+	/// </summary>
+	public KeyBindingScope Scope { get; set; } = KeyBindingScope.Focused;
+
+	/// <summary>
+	/// The key value as a Rune. This is the actual value of the key pressed, and is independent of the modifiers.
+	/// </summary>
+	/// <remarks>
+	/// If the key pressed is a letter (a-z or A-Z), this will be the upper or lower case letter depending on whether the shift key is pressed.
+	/// If the key is outside of the <see cref="KeyCode.CharMask"/> range, this will be <see langword="default"/>.
+	/// </remarks>
+	public Rune AsRune => ToRune (KeyCode);
+
+	/// <summary>
+	/// Converts a <see cref="KeyCode"/> to a <see cref="Rune"/>.
+	/// </summary>
+	/// <remarks>
+	/// If the key is a letter (a-z or A-Z), this will be the upper or lower case letter depending on whether the shift key is pressed.
+	/// If the key is outside of the <see cref="KeyCode.CharMask"/> range, this will be <see langword="default"/>.
+	/// </remarks>
+	/// <param name="key"></param>
+	/// <returns>The key converted to a rune. <see langword="default"/> if conversion is not possible.</returns>
+	public static Rune ToRune (KeyCode key)
+	{
+		if (key is KeyCode.Null or KeyCode.SpecialMask || key.HasFlag (KeyCode.CtrlMask) || key.HasFlag (KeyCode.AltMask)) {
+			return default;
+		}
+
+		// Extract the base key (removing modifier flags)
+		var baseKey = key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask;
+
+		switch (baseKey) {
+		case >= KeyCode.A and <= KeyCode.Z when !key.HasFlag (KeyCode.ShiftMask):
+			return new Rune ((char)(baseKey + 32));
+		case >= KeyCode.A and <= KeyCode.Z:
+			return new Rune ((char)baseKey);
+		case > KeyCode.Null and < KeyCode.A:
+			return new Rune ((char)baseKey);
+		}
+
+		if (Enum.IsDefined (typeof (KeyCode), baseKey)) {
+			return default;
+		}
+
+		return new Rune ((char)baseKey);
+	}
+
+	/// <summary>
+	/// Gets a value indicating whether the Shift key was pressed.
+	/// </summary>
+	/// <value><see langword="true"/> if is shift; otherwise, <see langword="false"/>.</value>
+	public bool IsShift => (KeyCode & KeyCode.ShiftMask) != 0;
+
+	/// <summary>
+	/// Gets a value indicating whether the Alt key was pressed (real or synthesized)
+	/// </summary>
+	/// <value><see langword="true"/> if is alternate; otherwise, <see langword="false"/>.</value>
+	public bool IsAlt => (KeyCode & KeyCode.AltMask) != 0;
+
+	/// <summary>
+	/// Gets a value indicating whether the Ctrl key was pressed.
+	/// </summary>
+	/// <value><see langword="true"/> if is ctrl; otherwise, <see langword="false"/>.</value>
+	public bool IsCtrl => (KeyCode & KeyCode.CtrlMask) != 0;
+
+	/// <summary>
+	/// Gets a value indicating whether the KeyCode is composed of a lower case letter from 'a' to 'z', independent of the shift key.
+	/// </summary>
+	/// <remarks>
+	/// IMPORTANT: Lowercase alpha keys are encoded in <see cref="Key.KeyCode"/> as values between 65 and 90 corresponding to
+	/// the un-shifted A to Z keys on a keyboard. Helper properties are provided these (e.g. <see cref="Key.A"/>, <see cref="Key.B"/>, etc.).
+	/// Even though the values are the same as the ASCII values for uppercase characters, these enum values represent *lowercase*, un-shifted characters.
+	/// </remarks>
+	public bool IsKeyCodeAtoZ => GetIsKeyCodeAtoZ (KeyCode);
+
+	/// <summary>
+	/// Tests if a KeyCode is composed of a lower case letter from 'a' to 'z', independent of the shift key.
+	/// </summary>
+	/// <remarks>
+	/// IMPORTANT: Lowercase alpha keys are encoded in <see cref="Key.KeyCode"/> as values between 65 and 90 corresponding to
+	/// the un-shifted A to Z keys on a keyboard. Helper properties are provided these (e.g. <see cref="Key.A"/>, <see cref="Key.B"/>, etc.).
+	/// Even though the values are the same as the ASCII values for uppercase characters, these enum values represent *lowercase*, un-shifted characters.
+	/// </remarks>
+	public static bool GetIsKeyCodeAtoZ (KeyCode keyCode)
+	{
+		if ((keyCode & KeyCode.AltMask) != 0 || (keyCode & KeyCode.CtrlMask) != 0) {
+			return false;
+		}
+
+		if ((keyCode & ~KeyCode.Space & ~KeyCode.ShiftMask) is >= KeyCode.A and <= KeyCode.Z) {
+			return true;
+		}
+
+		return (keyCode & KeyCode.CharMask) is >= KeyCode.A and <= KeyCode.Z;
+	}
+
+	/// <summary>
+	/// Indicates whether the <see cref="Key"/> is valid or not. 
+	/// </summary>
+	public bool IsValid => KeyCode is not (KeyCode.Null or KeyCode.Unknown);
+
+	/// <summary>
+	/// Helper for specifying a shifted <see cref="Key"/>.
+	/// <code>
+	/// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
+	/// </code>
+	/// </summary>
+	public Key WithShift => new (KeyCode | KeyCode.ShiftMask);
+
+	/// <summary>
+	/// Helper for removing a shift modifier from a <see cref="Key"/>.
+	/// <code>
+	/// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
+	/// var AltDelete = ControlAltDelete.NoCtrl;
+	/// </code>
+	/// </summary>
+	public Key NoShift => new (KeyCode & ~KeyCode.ShiftMask);
+
+	/// <summary>
+	/// Helper for specifying a shifted <see cref="Key"/>.
+	/// <code>
+	/// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
+	/// </code>
+	/// </summary>
+	public Key WithCtrl => new (KeyCode | KeyCode.CtrlMask);
+
+	/// <summary>
+	/// Helper for removing a shift modifier from a <see cref="Key"/>.
+	/// <code>
+	/// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
+	/// var AltDelete = ControlAltDelete.NoCtrl;
+	/// </code>
+	/// </summary>
+	public Key NoCtrl => new (KeyCode & ~KeyCode.CtrlMask);
+
+	/// <summary>
+	/// Helper for specifying a shifted <see cref="Key"/>.
+	/// <code>
+	/// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
+	/// </code>
+	/// </summary>
+	public Key WithAlt => new (KeyCode | KeyCode.AltMask);
+
+	/// <summary>
+	/// Helper for removing a shift modifier from a <see cref="Key"/>.
+	/// <code>
+	/// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
+	/// var AltDelete = ControlAltDelete.NoCtrl;
+	/// </code>
+	/// </summary>
+	public Key NoAlt => new (KeyCode & ~KeyCode.AltMask);
+
+	#region Operators
+	/// <summary>
+	/// Explicitly cast a <see cref="Key"/> to a <see cref="Rune"/>. The conversion is lossy. 
+	/// </summary>
+	/// <remarks>
+	/// Uses <see cref="AsRune"/>.
+	/// </remarks>
+	/// <param name="kea"></param>
+	public static explicit operator Rune (Key kea) => kea.AsRune;
+
+	/// <summary>
+	/// Explicitly cast <see cref="Key"/> to a <see langword="char"/>. The conversion is lossy. 
+	/// </summary>
+	/// <param name="kea"></param>
+	public static explicit operator char (Key kea) => (char)kea.AsRune.Value;
+
+	/// <summary>
+	/// Explicitly cast <see cref="Key"/> to a <see cref="KeyCode"/>. The conversion is lossy. 
+	/// </summary>
+	/// <param name="key"></param>
+	public static explicit operator KeyCode (Key key) => key.KeyCode;
+
+	/// <summary>
+	/// Cast <see cref="KeyCode"/> to a <see cref="Key"/>. 
+	/// </summary>
+	/// <param name="keyCode"></param>
+	public static implicit operator Key (KeyCode keyCode) => new (keyCode);
+
+
+	/// <summary>
+	/// Cast <see langword="char"/> to a <see cref="Key"/>. 
+	/// </summary>
+	/// <param name="ch"></param>
+	public static implicit operator Key (char ch) => new ((KeyCode)ch);
+
+	/// <inheritdoc/>
+	public override bool Equals (object obj) => obj is Key k && k.KeyCode == KeyCode;
+
+	bool IEquatable<Key>.Equals (Key other) => Equals ((object)other);
+
+	/// <inheritdoc/>
+	public override int GetHashCode () => (int)KeyCode;
+
+	/// <summary>
+	/// </summary>
+	/// <param name="a"></param>
+	/// <param name="b"></param>
+	/// <returns></returns>
+	public static bool operator == (Key a, Key b) => a?.KeyCode == b?.KeyCode;
+
+	/// <summary>
+	/// </summary>
+	/// <param name="a"></param>
+	/// <param name="b"></param>
+	/// <returns></returns>
+	public static bool operator != (Key a, Key b) => a?.KeyCode != b?.KeyCode;
+
+	/// <summary>
+	/// Compares two <see cref="Key"/>s for less-than.
+	/// </summary>
+	/// <param name="a"></param>
+	/// <param name="b"></param>
+	/// <returns></returns>
+	public static bool operator < (Key a, Key b) => a?.KeyCode < b?.KeyCode;
+
+	/// <summary>
+	/// Compares two <see cref="Key"/>s for greater-than.
+	/// </summary>
+	/// <param name="a"></param>
+	/// <param name="b"></param>
+	/// <returns></returns>
+	public static bool operator > (Key a, Key b) => a?.KeyCode > b?.KeyCode;
+
+	/// <summary>
+	/// Compares two <see cref="Key"/>s for greater-than-or-equal-to.
+	/// </summary>
+	/// <param name="a"></param>
+	/// <param name="b"></param>
+	/// <returns></returns>
+	public static bool operator <= (Key a, Key b) => a?.KeyCode <= b?.KeyCode;
+
+	/// <summary>
+	/// Compares two <see cref="Key"/>s for greater-than-or-equal-to.
+	/// </summary>
+	/// <param name="a"></param>
+	/// <param name="b"></param>
+	/// <returns></returns>
+	public static bool operator >= (Key a, Key b) => a?.KeyCode >= b?.KeyCode;
+	#endregion Operators
+
+	#region String conversion
+	/// <summary>
+	/// Pretty prints the KeyEvent
+	/// </summary>
+	/// <returns></returns>
+	public override string ToString () => ToString (KeyCode, (Rune)'+');
+
+	static string GetKeyString (KeyCode key)
+	{
+		if (key is KeyCode.Null or KeyCode.SpecialMask) {
+			return string.Empty;
+		}
+		// Extract the base key (removing modifier flags)
+		var baseKey = key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask;
+
+		if (!key.HasFlag (KeyCode.ShiftMask) && baseKey is >= KeyCode.A and <= KeyCode.Z) {
+			return ((char)(key + 32)).ToString ();
+		}
+
+		if (key is >= KeyCode.Space and < KeyCode.A) {
+			return ((char)key).ToString ();
+		}
+
+		string keyName = Enum.GetName (typeof (KeyCode), key);
+		return !string.IsNullOrEmpty (keyName) ? keyName : ((char)key).ToString ();
+	}
+
+
+	/// <summary>
+	/// Formats a <see cref="KeyCode"/> as a string using the default separator of '+'
+	/// </summary>
+	/// <param name="key">The key to format.</param>
+	/// <returns>The formatted string. If the key is a printable character, it will be returned as a string. Otherwise, the key name will be returned.</returns>
+	public static string ToString (KeyCode key) => ToString (key, (Rune)'+');
+
+	/// <summary>
+	/// Formats a <see cref="KeyCode"/> as a string.
+	/// </summary>
+	/// <param name="key">The key to format.</param>
+	/// <param name="separator">The character to use as a separator between modifier keys and and the key itself.</param>
+	/// <returns>The formatted string. If the key is a printable character, it will be returned as a string. Otherwise, the key name will be returned.</returns>
+	public static string ToString (KeyCode key, Rune separator)
+	{
+		if (key is KeyCode.Null) {
+			return string.Empty;
+		}
+
+		var sb = new StringBuilder ();
+
+		// Extract the base key (removing modifier flags)
+		var baseKey = key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask;
+
+		// Extract and handle modifiers
+		bool hasModifiers = false;
+		if ((key & KeyCode.CtrlMask) != 0) {
+			sb.Append ($"Ctrl{separator}");
+			hasModifiers = true;
+		}
+		if ((key & KeyCode.AltMask) != 0) {
+			sb.Append ($"Alt{separator}");
+			hasModifiers = true;
+		}
+		if ((key & KeyCode.ShiftMask) != 0 && !GetIsKeyCodeAtoZ (key)) {
+			sb.Append ($"Shift{separator}");
+			hasModifiers = true;
+		}
+
+		// Handle special cases and modifiers on their own
+		if (key != KeyCode.SpecialMask && (baseKey != KeyCode.Null || hasModifiers)) {
+			if ((key & KeyCode.SpecialMask) != 0 && (baseKey & ~KeyCode.Space) is >= KeyCode.A and <= KeyCode.Z) {
+				sb.Append (baseKey & ~KeyCode.Space);
+			} else {
+				// Append the actual key name
+				sb.Append (GetKeyString (baseKey));
+			}
+		}
+
+		string result = sb.ToString ();
+		result = TrimEndRune (result, separator);
+		return result;
+	}
+
+	static string TrimEndRune (string input, Rune runeToTrim)
+	{
+		// Convert the Rune to a string (which may be one or two chars)
+		string runeString = runeToTrim.ToString ();
+
+		if (input.EndsWith (runeString)) {
+			// Remove the rune from the end of the string
+			return input.Substring (0, input.Length - runeString.Length);
+		}
+
+		return input;
+	}
+
+	static readonly Dictionary<string, KeyCode> _modifierDict = new (comparer: StringComparer.InvariantCultureIgnoreCase) {
+		{ "Shift", KeyCode.ShiftMask },
+		{ "Ctrl", KeyCode.CtrlMask },
+		{ "Alt", KeyCode.AltMask }
+	};
+
+	/// <summary>
+	/// Converts the provided string to a new <see cref="Key"/> instance.
+	/// </summary>
+	/// <param name="text">The text to analyze. Formats supported are
+	/// "Ctrl+X", "Alt+X", "Shift+X", "Ctrl+Alt+X", "Ctrl+Shift+X", "Alt+Shift+X", "Ctrl+Alt+Shift+X", and "X".
+	/// </param>
+	/// <param name="key">The parsed value.</param>
+	/// <returns>A boolean value indicating whether parsing was successful.</returns>
+	/// <remarks>
+	/// </remarks>
+	public static bool TryParse (string text, [NotNullWhen (true)] out Key key)
+	{
+		if (string.IsNullOrEmpty (text)) {
+			key = new Key (KeyCode.Null);
+			return true;
+		}
+
+		key = null;
+
+		// Split the string into parts
+		string [] parts = text.Split ('+', '-');
+
+		if (parts.Length is 0 or > 4 || parts.Any (string.IsNullOrEmpty)) {
+			return false;
+		}
+
+		// if it's just a shift key
+		if (parts.Length == 1) {
+			switch (parts [0]) {
+			case "Ctrl":
+				key = new Key (KeyCode.CtrlKey);
+				return true;
+			case "Alt":
+				key = new Key (KeyCode.AltKey);
+				return true;
+			case "Shift":
+				key = new Key (KeyCode.ShiftKey);
+				return true;
+			}
+		}
+
+		var modifiers = KeyCode.Null;
+		for (int index = 0; index < parts.Length; index++) {
+			if (_modifierDict.TryGetValue (parts [index].ToLowerInvariant (), out var modifier)) {
+				modifiers |= modifier;
+				parts [index] = string.Empty; // eat it
+			}
+		}
+
+		// we now have the modifiers
+
+		string partNotFound = parts.FirstOrDefault (p => !string.IsNullOrEmpty (p), string.Empty);
+		var parsedKeyCode = KeyCode.Null;
+		int parsedInt = 0;
+		if (partNotFound.Length == 1) {
+			var keyCode = (KeyCode)partNotFound [0];
+			// if it's a single digit int, treat it as such
+			if (int.TryParse (partNotFound,
+				System.Globalization.NumberStyles.Integer,
+				System.Globalization.CultureInfo.InvariantCulture,
+				out parsedInt)) {
+				keyCode = (KeyCode)((int)KeyCode.D0 + parsedInt);
+			} else if (Enum.TryParse (partNotFound, false, out parsedKeyCode)) {
+				if (parsedKeyCode != KeyCode.Null) {
+					if (parsedKeyCode is >= KeyCode.A and <= KeyCode.Z && modifiers == 0) {
+						key = new Key (parsedKeyCode | KeyCode.ShiftMask);
+						return true;
+					}
+					key = new Key ((KeyCode)parsedKeyCode | modifiers);
+					return true;
+				}
+			}
+			key = new Key (keyCode | modifiers);
+			return true;
+		}
+
+		if (Enum.TryParse (partNotFound, true, out parsedKeyCode)) {
+			if (parsedKeyCode != KeyCode.Null) {
+				if (parsedKeyCode is >= KeyCode.A and <= KeyCode.Z && modifiers == 0) {
+					key = new Key (parsedKeyCode | KeyCode.ShiftMask);
+					return true;
+				}
+				key = new Key (parsedKeyCode | modifiers);
+				return true;
+			}
+		}
+
+		// if it's a number int, treat it as a unicode value
+		if (int.TryParse (partNotFound,
+			System.Globalization.NumberStyles.Number,
+			System.Globalization.CultureInfo.InvariantCulture, out parsedInt)) {
+			if (!Rune.IsValid (parsedInt)) {
+				return false;
+			}
+			if ((KeyCode)parsedInt is >= KeyCode.A and <= KeyCode.Z && modifiers == 0) {
+				key = new Key ((KeyCode)parsedInt | KeyCode.ShiftMask);
+				return true;
+			}
+			key = new Key ((KeyCode)parsedInt);
+			return true;
+		}
+
+		if (!Enum.TryParse (partNotFound, true, out parsedKeyCode)) {
+			return false;
+		}
+
+		if (GetIsKeyCodeAtoZ (parsedKeyCode)) {
+			key = new Key (parsedKeyCode | modifiers & ~KeyCode.Space);
+			return true;
+		}
+
+		return false;
+	}
+	#endregion
+
+
+	#region Standard Key Definitions
+	/// <summary>
+	/// An uninitialized The <see cref="Key"/> object.
+	/// </summary>
+	public new static readonly Key Empty = new ();
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the Backspace key.
+	/// </summary>
+	public static readonly Key Backspace = new (KeyCode.Backspace);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the tab key (forwards tab key).
+	/// </summary>
+	public static readonly Key Tab = new (KeyCode.Tab);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the return key.
+	/// </summary>
+	public static readonly Key Enter = new (KeyCode.Enter);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the clear key.
+	/// </summary>
+	public static readonly Key Clear = new (KeyCode.Clear);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the Shift key.
+	/// </summary>
+	public static readonly Key Shift = new (KeyCode.ShiftKey);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the Ctrl key.
+	/// </summary>
+	public static readonly Key Ctrl = new (KeyCode.CtrlKey);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the Alt key.
+	/// </summary>
+	public static readonly Key Alt = new (KeyCode.AltKey);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the CapsLock key.
+	/// </summary>
+	public static readonly Key CapsLock = new (KeyCode.CapsLock);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the Escape key.
+	/// </summary>
+	public static readonly Key Esc = new (KeyCode.Esc);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the Space bar key.
+	/// </summary>
+	public static readonly Key Space = new (KeyCode.Space);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for 0 key.
+	/// </summary>
+	public static readonly Key D0 = new (KeyCode.D0);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for 1 key.
+	/// </summary>
+	public static readonly Key D1 = new (KeyCode.D1);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for 2 key.
+	/// </summary>
+	public static readonly Key D2 = new (KeyCode.D2);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for 3 key.
+	/// </summary>
+	public static readonly Key D3 = new (KeyCode.D3);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for 4 key.
+	/// </summary>
+	public static readonly Key D4 = new (KeyCode.D4);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for 5 key.
+	/// </summary>
+	public static readonly Key D5 = new (KeyCode.D5);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for 6 key.
+	/// </summary>
+	public static readonly Key D6 = new (KeyCode.D6);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for 7 key.
+	/// </summary>
+	public static readonly Key D7 = new (KeyCode.D7);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for 8 key.
+	/// </summary>
+	public static readonly Key D8 = new (KeyCode.D8);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for 9 key.
+	/// </summary>
+	public static readonly Key D9 = new (KeyCode.D9);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the A key (un-shifted). Use <c>Key.A.WithShift</c> for uppercase 'A'.
+	/// </summary>
+	public static readonly Key A = new (KeyCode.A);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the B key (un-shifted). Use <c>Key.B.WithShift</c> for uppercase 'B'.
+	/// </summary>
+	public static readonly Key B = new (KeyCode.B);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the C key (un-shifted). Use <c>Key.C.WithShift</c> for uppercase 'C'.
+	/// </summary>
+	public static readonly Key C = new (KeyCode.C);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the D key (un-shifted). Use <c>Key.D.WithShift</c> for uppercase 'D'.
+	/// </summary>
+	public static readonly Key D = new (KeyCode.D);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the E key (un-shifted). Use <c>Key.E.WithShift</c> for uppercase 'E'.
+	/// </summary>
+	public static readonly Key E = new (KeyCode.E);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the F key (un-shifted). Use <c>Key.F.WithShift</c> for uppercase 'F'.
+	/// </summary>
+	public static readonly Key F = new (KeyCode.F);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the G key (un-shifted). Use <c>Key.G.WithShift</c> for uppercase 'G'.
+	/// </summary>
+	public static readonly Key G = new (KeyCode.G);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the H key (un-shifted). Use <c>Key.H.WithShift</c> for uppercase 'H'.
+	/// </summary>
+	public static readonly Key H = new (KeyCode.H);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the I key (un-shifted). Use <c>Key.I.WithShift</c> for uppercase 'I'.
+	/// </summary>
+	public static readonly Key I = new (KeyCode.I);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the J key (un-shifted). Use <c>Key.J.WithShift</c> for uppercase 'J'.
+	/// </summary>
+	public static readonly Key J = new (KeyCode.J);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the K key (un-shifted). Use <c>Key.K.WithShift</c> for uppercase 'K'.
+	/// </summary>
+	public static readonly Key K = new (KeyCode.K);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the L key (un-shifted). Use <c>Key.L.WithShift</c> for uppercase 'L'.
+	/// </summary>
+	public static readonly Key L = new (KeyCode.L);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the M key (un-shifted). Use <c>Key.M.WithShift</c> for uppercase 'M'.
+	/// </summary>
+	public static readonly Key M = new (KeyCode.M);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the N key (un-shifted). Use <c>Key.N.WithShift</c> for uppercase 'N'.
+	/// </summary>
+	public static readonly Key N = new (KeyCode.N);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the O key (un-shifted). Use <c>Key.O.WithShift</c> for uppercase 'O'.
+	/// </summary>
+	public static readonly Key O = new (KeyCode.O);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the P key (un-shifted). Use <c>Key.P.WithShift</c> for uppercase 'P'.
+	/// </summary>
+	public static readonly Key P = new (KeyCode.P);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the Q key (un-shifted). Use <c>Key.Q.WithShift</c> for uppercase 'Q'.
+	/// </summary>
+	public static readonly Key Q = new (KeyCode.Q);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the R key (un-shifted). Use <c>Key.R.WithShift</c> for uppercase 'R'.
+	/// </summary>
+	public static readonly Key R = new (KeyCode.R);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the S key (un-shifted). Use <c>Key.S.WithShift</c> for uppercase 'S'.
+	/// </summary>
+	public static readonly Key S = new (KeyCode.S);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the T key (un-shifted). Use <c>Key.T.WithShift</c> for uppercase 'T'.
+	/// </summary>
+	public static readonly Key T = new (KeyCode.T);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the U key (un-shifted). Use <c>Key.U.WithShift</c> for uppercase 'U'.
+	/// </summary>
+	public static readonly Key U = new (KeyCode.U);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the V key (un-shifted). Use <c>Key.V.WithShift</c> for uppercase 'V'.
+	/// </summary>
+	public static readonly Key V = new (KeyCode.V);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the W key (un-shifted). Use <c>Key.W.WithShift</c> for uppercase 'W'.
+	/// </summary>
+	public static readonly Key W = new (KeyCode.W);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the X key (un-shifted). Use <c>Key.X.WithShift</c> for uppercase 'X'.
+	/// </summary>
+	public static readonly Key X = new (KeyCode.X);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the Y key (un-shifted). Use <c>Key.Y.WithShift</c> for uppercase 'Y'.
+	/// </summary>
+	public static readonly Key Y = new (KeyCode.Y);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the Z key (un-shifted). Use <c>Key.Z.WithShift</c> for uppercase 'Z'.
+	/// </summary>
+	public static readonly Key Z = new (KeyCode.Z);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the Delete key.
+	/// </summary>
+	public static readonly Key Delete = new (KeyCode.Delete);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for the Cursor up key.
+	/// </summary>
+	public static readonly Key CursorUp = new (KeyCode.CursorUp);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for Cursor down key.
+	/// </summary>
+	public static readonly Key CursorDown = new (KeyCode.CursorDown);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for Cursor left key.
+	/// </summary>
+	public static readonly Key CursorLeft = new (KeyCode.CursorLeft);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for Cursor right key.
+	/// </summary>
+	public static readonly Key CursorRight = new (KeyCode.CursorRight);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for Page Up key.
+	/// </summary>
+	public static readonly Key PageUp = new (KeyCode.PageUp);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for Page Down key.
+	/// </summary>
+	public static readonly Key PageDown = new (KeyCode.PageDown);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for Home key.
+	/// </summary>
+	public static readonly Key Home = new (KeyCode.Home);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for End key.
+	/// </summary>
+	public static readonly Key End = new (KeyCode.End);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for Insert Character key.
+	/// </summary>
+	public static readonly Key InsertChar = new (KeyCode.InsertChar);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for Delete Character key.
+	/// </summary>
+	public static readonly Key DeleteChar = new (KeyCode.DeleteChar);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for Print Screen key.
+	/// </summary>
+	public static readonly Key PrintScreen = new (KeyCode.PrintScreen);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for F1 key.
+	/// </summary>
+	public static readonly Key F1 = new (KeyCode.F1);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for F2 key.
+	/// </summary>
+	public static readonly Key F2 = new (KeyCode.F2);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for F3 key.
+	/// </summary>
+	public static readonly Key F3 = new (KeyCode.F3);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for F4 key.
+	/// </summary>
+	public static readonly Key F4 = new (KeyCode.F4);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for F5 key.
+	/// </summary>
+	public static readonly Key F5 = new (KeyCode.F5);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for F6 key.
+	/// </summary>
+	public static readonly Key F6 = new (KeyCode.F6);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for F7 key.
+	/// </summary>
+	public static readonly Key F7 = new (KeyCode.F7);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for F8 key.
+	/// </summary>
+	public static readonly Key F8 = new (KeyCode.F8);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for F9 key.
+	/// </summary>
+	public static readonly Key F9 = new (KeyCode.F9);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for F10 key.
+	/// </summary>
+	public static readonly Key F10 = new (KeyCode.F10);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for F11 key.
+	/// </summary>
+	public static readonly Key F11 = new (KeyCode.F11);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for F12 key.
+	/// </summary>
+	public static readonly Key F12 = new (KeyCode.F12);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for F13 key.
+	/// </summary>
+	public static readonly Key F13 = new (KeyCode.F13);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for F14 key.
+	/// </summary>
+	public static readonly Key F14 = new (KeyCode.F14);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for F15 key.
+	/// </summary>
+	public static readonly Key F15 = new (KeyCode.F15);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for F16 key.
+	/// </summary>
+	public static readonly Key F16 = new (KeyCode.F16);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for F17 key.
+	/// </summary>
+	public static readonly Key F17 = new (KeyCode.F17);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for F18 key.
+	/// </summary>
+	public static readonly Key F18 = new (KeyCode.F18);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for F19 key.
+	/// </summary>
+	public static readonly Key F19 = new (KeyCode.F19);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for F20 key.
+	/// </summary>
+	public static readonly Key F20 = new (KeyCode.F20);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for F21 key.
+	/// </summary>
+	public static readonly Key F21 = new (KeyCode.F21);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for F22 key.
+	/// </summary>
+	public static readonly Key F22 = new (KeyCode.F22);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for F23 key.
+	/// </summary>
+	public static readonly Key F23 = new (KeyCode.F23);
+
+	/// <summary>
+	/// The <see cref="Key"/> object for F24 key.
+	/// </summary>
+	public static readonly Key F24 = new (KeyCode.F24);
+	#endregion
+}

+ 286 - 0
Terminal.Gui/Input/KeyBinding.cs

@@ -0,0 +1,286 @@
+// These classes use a key binding system based on the design implemented in Scintilla.Net which is an
+// MIT licensed open source project https://github.com/jacobslusser/ScintillaNET/blob/master/src/ScintillaNET/Command.cs
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Terminal.Gui;
+
+/// <summary>
+/// Defines the scope of a <see cref="Command"/> that has been bound to a key with <see cref="KeyBindings.Add(Key, Terminal.Gui.Command[])"/>.
+/// </summary>
+/// <remarks>
+/// <para>
+/// Key bindings are scoped to the most-focused view (<see cref="Focused"/>) by default.
+/// </para>
+/// </remarks>
+public enum KeyBindingScope {
+	/// <summary>
+	/// The key binding is scoped to just the view that has focus.
+	/// </summary>
+	Focused = 0,
+
+	/// <summary>
+	/// The key binding is scoped to the View's SuperView and will be triggered even when the View does not have focus, as long as the
+	/// SuperView does have focus. This is typically used for <see cref="View.HotKey"/>s.
+	/// <remarks>
+	/// <para>
+	/// Use for Views such as MenuBar and StatusBar which provide commands (shortcuts etc...) that trigger even when not focused.
+	/// </para>
+	/// <para>
+	/// HotKey-scoped key bindings are only invoked if the key down event was not handled by the focused view or any of its subviews.
+	/// </para>
+	/// </remarks>
+	/// </summary>
+	HotKey,
+
+	/// <summary>
+	/// The key binding will be triggered regardless of which view has focus. This is typically used for global commands.
+	/// </summary>
+	/// <remarks>
+	/// Application-scoped key bindings are only invoked if the key down event was not handled by the focused view or any of its subviews,
+	/// and if the key down event was not bound to a <see cref="View.HotKey"/>.
+	/// </remarks>
+	Application
+}
+
+/// <summary>
+/// Provides a collection of <see cref="Command"/> objects that are scoped to <see cref="KeyBindingScope"/>.
+/// </summary>
+public class KeyBinding {
+	/// <summary>
+	/// Initializes a new instance.
+	/// </summary>
+	/// <param name="commands"></param>
+	/// <param name="scope"></param>
+	public KeyBinding (Command [] commands, KeyBindingScope scope)
+	{
+		Commands = commands;
+		Scope = scope;
+	}
+
+	/// <summary>
+	/// The actions which can be performed by the application or bound to keys in a <see cref="View"/> control.
+	/// </summary>
+	public Command [] Commands { get; set; }
+
+	/// <summary>
+	/// The scope of the <see cref="Commands"/> bound to a key.
+	/// </summary>
+	public KeyBindingScope Scope { get; set; }
+}
+
+/// <summary>
+/// A class that provides a collection of <see cref="KeyBinding"/> objects bound to a <see cref="Key"/>.
+/// </summary>
+public class KeyBindings {
+	/// <summary>
+	/// The collection of <see cref="KeyBinding"/> objects.
+	/// </summary>
+	public Dictionary<Key, KeyBinding> Bindings { get; } = new ();
+
+	/// <summary>
+	/// Adds a <see cref="KeyBinding"/> to the collection.
+	/// </summary>
+	/// <param name="key"></param>
+	/// <param name="binding"></param>
+	public void Add (Key key, KeyBinding binding) => Bindings.Add (key, binding);
+
+	/// <summary>
+	/// Removes a <see cref="KeyBinding"/> from the collection.
+	/// </summary>
+	/// <param name="key"></param>
+	public void Remove (Key key) => Bindings.Remove (key);
+
+	/// <summary>
+	/// Removes all <see cref="KeyBinding"/> objects from the collection.
+	/// </summary>
+	public void Clear () => Bindings.Clear ();
+
+	/// <summary>
+	/// <para>
+	/// Adds a new key combination that will trigger the commands in <paramref name="commands"/>.
+	/// </para>
+	/// <para>
+	/// If the key is already bound to a different array of <see cref="Command"/>s it will be
+	/// rebound <paramref name="commands"/>.</para>
+	/// </summary>
+	/// <remarks>
+	/// Commands are only ever applied to the current <see cref="View"/> (i.e. this feature
+	/// cannot be used to switch focus to another view and perform multiple commands there).
+	/// </remarks>
+	/// <param name="key">
+	/// The key to check.
+	/// </param>
+	/// <param name="scope">The scope for the command.</param>
+	/// <param name="commands">The command to invoked on the <see cref="View"/> when <paramref name="key"/> is pressed.
+	/// When multiple commands are provided,they will be applied in sequence. The bound <paramref name="key"/> strike
+	/// will be consumed if any took effect.</param>
+	public void Add (Key key, KeyBindingScope scope, params Command [] commands)
+	{
+		if (commands.Length == 0) {
+			throw new ArgumentException (@"At least one command must be specified", nameof (commands));
+		}
+
+		if (key == null || !key.IsValid) {
+			//throw new ArgumentException ("Invalid Key", nameof (commands));
+			return;
+		}
+
+		if (TryGet (key, out var _)) {
+			Bindings [key] = new KeyBinding (commands, scope);
+		} else {
+			Bindings.Add (key, new KeyBinding (commands, scope));
+		}
+	}
+
+	/// <summary>
+	/// <para>
+	/// Adds a new key combination that will trigger the commands in <paramref name="commands"/>
+	/// (if supported by the View - see <see cref="View.GetSupportedCommands"/>).
+	/// </para>
+	/// <para>
+	/// This is a helper function for <see cref="Add(Key,KeyBindingScope,Terminal.Gui.Command[])"/>
+	/// for <see cref="KeyBindingScope.Focused"/> scoped commands.
+	/// </para>
+	/// <para>
+	/// If the key is already bound to a different array of <see cref="Command"/>s it will be
+	/// rebound <paramref name="commands"/>.</para>
+	/// </summary>
+	/// <remarks>
+	/// Commands are only ever applied to the current <see cref="View"/> (i.e. this feature
+	/// cannot be used to switch focus to another view and perform multiple commands there).
+	/// </remarks>
+	/// <param name="key">
+	/// The key to check.
+	/// </param>
+	/// <param name="commands">The command to invoked on the <see cref="View"/> when <paramref name="key"/> is pressed.
+	/// When multiple commands are provided,they will be applied in sequence. The bound <paramref name="key"/> strike
+	/// will be consumed if any took effect.</param>
+	public void Add (Key key, params Command [] commands) => Add (key, KeyBindingScope.Focused, commands);
+
+	/// <summary>
+	/// Replaces a key combination already bound to a set of <see cref="Command"/>s.
+	/// </summary>
+	/// <remarks>
+	/// </remarks>
+	/// <param name="fromKey">The key to be replaced.</param>
+	/// <param name="toKey">The new key to be used.</param>
+	public void Replace (Key fromKey, Key toKey)
+	{
+		if (!TryGet (fromKey, out var _)) {
+			return;
+		}
+		var value = Bindings [fromKey];
+		Bindings.Remove (fromKey);
+		Bindings [toKey] = value;
+	}
+
+	/// <summary>
+	/// Gets the commands bound with the specified Key.
+	/// </summary>
+	/// <remarks>
+	/// </remarks>
+	/// <param name="key">
+	/// The key to check.
+	/// </param>
+	/// <param name="binding">
+	/// When this method returns, contains the commands bound with the specified Key, if the Key is found;
+	/// otherwise, null. This parameter is passed uninitialized.
+	/// </param>
+	/// <returns>
+	/// <see langword="true"/> if the Key is bound; otherwise <see langword="false"/>.
+	/// </returns>
+	public bool TryGet (Key key, out KeyBinding binding)
+	{
+		if (key.IsValid) {
+			return Bindings.TryGetValue (key, out binding);
+		}
+		binding = new KeyBinding (Array.Empty<Command> (), KeyBindingScope.Focused);
+		return false;
+	}
+
+	/// <summary>
+	/// Gets the <see cref="KeyBinding"/> for the specified <see cref="Key"/>.
+	/// </summary>
+	/// <param name="key"></param>
+	/// <returns></returns>
+	public KeyBinding Get (Key key) => TryGet (key, out var binding) ? binding : null;
+
+	/// <summary>
+	/// Gets the commands bound with the specified Key that are scoped to a particular scope.
+	/// </summary>
+	/// <remarks>
+	/// </remarks>
+	/// <param name="key">
+	/// The key to check.
+	/// </param>
+	/// <param name="scope">the scope to filter on</param>
+	/// <param name="binding">
+	/// When this method returns, contains the commands bound with the specified Key, if the Key is found;
+	/// otherwise, null. This parameter is passed uninitialized.
+	/// </param>
+	/// <returns>
+	/// <see langword="true"/> if the Key is bound; otherwise <see langword="false"/>.
+	/// </returns>
+	public bool TryGet (Key key, KeyBindingScope scope, out KeyBinding binding)
+	{
+		if (key.IsValid && Bindings.TryGetValue (key, out binding)) {
+			if (binding.Scope == scope) {
+				return true;
+			}
+		}
+		binding = new KeyBinding (Array.Empty<Command> (), KeyBindingScope.Focused);
+		return false;
+	}
+
+	/// <summary>
+	/// Gets the <see cref="KeyBinding"/> for the specified <see cref="Key"/>.
+	/// </summary>
+	/// <param name="key"></param>
+	/// <param name="scope"></param>
+	/// <returns></returns>
+	public KeyBinding Get (Key key, KeyBindingScope scope) => TryGet (key, scope, out var binding) ? binding : null;
+
+	/// <summary>
+	/// Gets the array of <see cref="Command"/>s bound to <paramref name="key"/> if it exists.
+	/// </summary>
+	/// <param name="key">
+	/// The key to check.
+	/// </param>
+	/// <returns>The array of <see cref="Command"/>s if <paramref name="key"/> is bound. An empty <see cref="Command"/> array if not.</returns>
+	public Command [] GetCommands (Key key)
+	{
+		if (TryGet (key, out var bindings)) {
+			return bindings.Commands;
+		}
+		return Array.Empty<Command> ();
+	}
+
+	/// <summary>
+	/// Removes all key bindings that trigger the given command set. Views can have multiple different
+	/// keys bound to the same command sets and this method will clear all of them.
+	/// </summary>
+	/// <param name="command"></param>
+	public void Clear (params Command [] command)
+	{
+		foreach (var kvp in Bindings.Where (kvp => kvp.Value.Commands.SequenceEqual (command)).ToArray ()) {
+			Bindings.Remove (kvp.Key);
+		}
+	}
+
+	/// <summary>
+	/// Gets the Key used by a set of commands.
+	/// </summary>
+	/// <remarks>
+	/// </remarks>
+	/// <param name="commands">The set of commands to search.</param>
+	/// <returns>The <see cref="Key"/> used by a <see cref="Command"/></returns>
+	/// <exception cref="InvalidOperationException">If no matching set of commands was found.</exception>
+	public Key GetKeyFromCommands (params Command [] commands)
+	{
+		return Bindings.First (a => a.Value.Commands.SequenceEqual (commands)).Key;
+	}
+}
+

+ 24 - 25
Terminal.Gui/Input/KeyChangedEventArgs.cs

@@ -1,34 +1,33 @@
 using System;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui;
+
+/// <summary>
+/// Event args for when a <see cref="Key"/> is changed from
+/// one value to a new value (e.g. in <see cref="View.HotKeyChanged"/>)
+/// </summary>
+public class KeyChangedEventArgs : EventArgs {
 
 	/// <summary>
-	/// Event args for when a <see cref="Key"/> is changed from
-	/// one value to a new value (e.g. in <see cref="View.HotKeyChanged"/>)
+	/// Gets the old <see cref="Key"/> that was set before the event.
+	/// Use <see cref="Key.Empty"/> to check for empty.
 	/// </summary>
-	public class KeyChangedEventArgs : EventArgs {
-
-		/// <summary>
-		/// Gets the old <see cref="Key"/> that was set before the event.
-		/// Use <see cref="Key.Null"/> to check for empty.
-		/// </summary>
-		public Key OldKey { get; }
+	public Key OldKey { get; }
 
-		/// <summary>
-		/// Gets the new <see cref="Key"/> that is being used.
-		/// Use <see cref="Key.Null"/> to check for empty.
-		/// </summary>
-		public Key NewKey { get; }
+	/// <summary>
+	/// Gets the new <see cref="Key"/> that is being used.
+	/// Use <see cref="Key.Empty"/> to check for empty.
+	/// </summary>
+	public Key NewKey { get; }
 
-		/// <summary>
-		/// Creates a new instance of the <see cref="KeyChangedEventArgs"/> class
-		/// </summary>
-		/// <param name="oldKey"></param>
-		/// <param name="newKey"></param>
-		public KeyChangedEventArgs (Key oldKey, Key newKey)
-		{
-			this.OldKey = oldKey;
-			this.NewKey = newKey;
-		}
+	/// <summary>
+	/// Creates a new instance of the <see cref="KeyChangedEventArgs"/> class
+	/// </summary>
+	/// <param name="oldKey"></param>
+	/// <param name="newKey"></param>
+	public KeyChangedEventArgs (Key oldKey, Key newKey)
+	{
+		this.OldKey = oldKey;
+		this.NewKey = newKey;
 	}
 }

+ 0 - 24
Terminal.Gui/Input/KeyEventEventArgs.cs

@@ -1,24 +0,0 @@
-using System;
-
-namespace Terminal.Gui {
-
-	/// <summary>
-	/// Defines the event arguments for <see cref="KeyEvent"/>
-	/// </summary>
-	public class KeyEventEventArgs : EventArgs {
-		/// <summary>
-		/// Constructs.
-		/// </summary>
-		/// <param name="ke"></param>
-		public KeyEventEventArgs (KeyEvent ke) => KeyEvent = ke;
-		/// <summary>
-		/// The <see cref="KeyEvent"/> for the event.
-		/// </summary>
-		public KeyEvent KeyEvent { get; set; }
-		/// <summary>
-		/// Indicates if the current Key event has already been processed and the driver should stop notifying any other event subscriber.
-		/// Its important to set this value to true specially when updating any View's layout from inside the subscriber method.
-		/// </summary>
-		public bool Handled { get; set; } = false;
-	}
-}

+ 185 - 0
Terminal.Gui/Input/Mouse.cs

@@ -0,0 +1,185 @@
+using System;
+
+namespace Terminal.Gui;
+
+/// <summary>
+/// Mouse flags reported in <see cref="MouseEvent"/>.
+/// </summary>
+/// <remarks>
+/// They just happen to map to the ncurses ones.
+/// </remarks>
+[Flags]
+public enum MouseFlags {
+	/// <summary>
+	/// The first mouse button was pressed.
+	/// </summary>
+	Button1Pressed = unchecked((int)0x2),
+	/// <summary>
+	/// The first mouse button was released.
+	/// </summary>
+	Button1Released = unchecked((int)0x1),
+	/// <summary>
+	/// The first mouse button was clicked (press+release).
+	/// </summary>
+	Button1Clicked = unchecked((int)0x4),
+	/// <summary>
+	/// The first mouse button was double-clicked.
+	/// </summary>
+	Button1DoubleClicked = unchecked((int)0x8),
+	/// <summary>
+	/// The first mouse button was triple-clicked.
+	/// </summary>
+	Button1TripleClicked = unchecked((int)0x10),
+	/// <summary>
+	/// The second mouse button was pressed.
+	/// </summary>
+	Button2Pressed = unchecked((int)0x80),
+	/// <summary>
+	/// The second mouse button was released.
+	/// </summary>
+	Button2Released = unchecked((int)0x40),
+	/// <summary>
+	/// The second mouse button was clicked (press+release).
+	/// </summary>
+	Button2Clicked = unchecked((int)0x100),
+	/// <summary>
+	/// The second mouse button was double-clicked.
+	/// </summary>
+	Button2DoubleClicked = unchecked((int)0x200),
+	/// <summary>
+	/// The second mouse button was triple-clicked.
+	/// </summary>
+	Button2TripleClicked = unchecked((int)0x400),
+	/// <summary>
+	/// The third mouse button was pressed.
+	/// </summary>
+	Button3Pressed = unchecked((int)0x2000),
+	/// <summary>
+	/// The third mouse button was released.
+	/// </summary>
+	Button3Released = unchecked((int)0x1000),
+	/// <summary>
+	/// The third mouse button was clicked (press+release).
+	/// </summary>
+	Button3Clicked = unchecked((int)0x4000),
+	/// <summary>
+	/// The third mouse button was double-clicked.
+	/// </summary>
+	Button3DoubleClicked = unchecked((int)0x8000),
+	/// <summary>
+	/// The third mouse button was triple-clicked.
+	/// </summary>
+	Button3TripleClicked = unchecked((int)0x10000),
+	/// <summary>
+	/// The fourth mouse button was pressed.
+	/// </summary>
+	Button4Pressed = unchecked((int)0x80000),
+	/// <summary>
+	/// The fourth mouse button was released.
+	/// </summary>
+	Button4Released = unchecked((int)0x40000),
+	/// <summary>
+	/// The fourth button was clicked (press+release).
+	/// </summary>
+	Button4Clicked = unchecked((int)0x100000),
+	/// <summary>
+	/// The fourth button was double-clicked.
+	/// </summary>
+	Button4DoubleClicked = unchecked((int)0x200000),
+	/// <summary>
+	/// The fourth button was triple-clicked.
+	/// </summary>
+	Button4TripleClicked = unchecked((int)0x400000),
+	/// <summary>
+	/// Flag: the shift key was pressed when the mouse button took place.
+	/// </summary>
+	ButtonShift = unchecked((int)0x2000000),
+	/// <summary>
+	/// Flag: the ctrl key was pressed when the mouse button took place.
+	/// </summary>
+	ButtonCtrl = unchecked((int)0x1000000),
+	/// <summary>
+	/// Flag: the alt key was pressed when the mouse button took place.
+	/// </summary>
+	ButtonAlt = unchecked((int)0x4000000),
+	/// <summary>
+	/// The mouse position is being reported in this event.
+	/// </summary>
+	ReportMousePosition = unchecked((int)0x8000000),
+	/// <summary>
+	/// Vertical button wheeled up.
+	/// </summary>
+	WheeledUp = unchecked((int)0x10000000),
+	/// <summary>
+	/// Vertical button wheeled down.
+	/// </summary>
+	WheeledDown = unchecked((int)0x20000000),
+	/// <summary>
+	/// Vertical button wheeled up while pressing ButtonShift.
+	/// </summary>
+	WheeledLeft = ButtonShift | WheeledUp,
+	/// <summary>
+	/// Vertical button wheeled down while pressing ButtonShift.
+	/// </summary>
+	WheeledRight = ButtonShift | WheeledDown,
+	/// <summary>
+	/// Mask that captures all the events.
+	/// </summary>
+	AllEvents = unchecked((int)0x7ffffff),
+}
+
+// TODO: Merge MouseEvent and MouseEventEventArgs into a single class.
+
+/// <summary>
+/// Low-level construct that conveys the details of mouse events, such
+/// as coordinates and button state, from ConsoleDrivers up to <see cref="Application"/> and
+/// Views.
+/// </summary>
+/// <remarks>The <see cref="Application"/> class includes the <see cref="Application.MouseEvent"/>
+/// Action which takes a MouseEvent argument.</remarks>
+public class MouseEvent {
+	/// <summary>
+	/// The X (column) location for the mouse event.
+	/// </summary>
+	public int X { get; set; }
+
+	/// <summary>
+	/// The Y (column) location for the mouse event.
+	/// </summary>
+	public int Y { get; set; }
+
+	/// <summary>
+	/// Flags indicating the kind of mouse event that is being posted.
+	/// </summary>
+	public MouseFlags Flags { get; set; }
+
+	/// <summary>
+	/// The offset X (column) location for the mouse event.
+	/// </summary>
+	public int OfX { get; set; }
+
+	/// <summary>
+	/// The offset Y (column) location for the mouse event.
+	/// </summary>
+	public int OfY { get; set; }
+
+	/// <summary>
+	/// The current view at the location for the mouse event.
+	/// </summary>
+	public View View { get; set; }
+
+	/// <summary>
+	/// Indicates if the current mouse event has already been processed and the driver should stop notifying any other event subscriber.
+	/// Its important to set this value to true specially when updating any View's layout from inside the subscriber method.
+	/// </summary>
+	public bool Handled { get; set; }
+
+	/// <summary>
+	/// Returns a <see cref="T:System.String"/> that represents the current <see cref="MouseEvent"/>.
+	/// </summary>
+	/// <returns>A <see cref="T:System.String"/> that represents the current <see cref="MouseEvent"/>.</returns>
+	public override string ToString ()
+	{
+		return $"({X},{Y}:{Flags}";
+	}
+}

+ 151 - 255
Terminal.Gui/Input/Responder.cs

@@ -18,286 +18,182 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Reflection;
 
-namespace Terminal.Gui {
-	/// <summary>
-	/// Responder base class implemented by objects that want to participate on keyboard and mouse input.
-	/// </summary>
-	public class Responder : IDisposable {
-		bool disposedValue;
+namespace Terminal.Gui;
+/// <summary>
+/// Responder base class implemented by objects that want to participate on keyboard and mouse input.
+/// </summary>
+public class Responder : IDisposable {
+	bool disposedValue;
 
 #if DEBUG_IDISPOSABLE
-		/// <summary>
-		/// For debug purposes to verify objects are being disposed properly
-		/// </summary>
-		public bool WasDisposed = false;
-		/// <summary>
-		/// For debug purposes to verify objects are being disposed properly
-		/// </summary>
-		public int DisposedCount = 0;
-		/// <summary>
-		/// For debug purposes
-		/// </summary>
-		public static List<Responder> Instances = new List<Responder> ();
-		/// <summary>
-		/// For debug purposes
-		/// </summary>
-		public Responder ()
-		{
-			Instances.Add (this);
-		}
+	/// <summary>
+	/// For debug purposes to verify objects are being disposed properly
+	/// </summary>
+	public bool WasDisposed = false;
+	/// <summary>
+	/// For debug purposes to verify objects are being disposed properly
+	/// </summary>
+	public int DisposedCount = 0;
+	/// <summary>
+	/// For debug purposes
+	/// </summary>
+	public static List<Responder> Instances = new List<Responder> ();
+	/// <summary>
+	/// For debug purposes
+	/// </summary>
+	public Responder ()
+	{
+		Instances.Add (this);
+	}
 #endif
 
-		/// <summary>
-		/// Gets or sets a value indicating whether this <see cref="Responder"/> can focus.
-		/// </summary>
-		/// <value><c>true</c> if can focus; otherwise, <c>false</c>.</value>
-		public virtual bool CanFocus { get; set; }
-
-		/// <summary>
-		/// Gets or sets a value indicating whether this <see cref="Responder"/> has focus.
-		/// </summary>
-		/// <value><c>true</c> if has focus; otherwise, <c>false</c>.</value>
-		public virtual bool HasFocus { get; }
-
-		/// <summary>
-		/// Gets or sets a value indicating whether this <see cref="Responder"/> can respond to user interaction.
-		/// </summary>
-		public virtual bool Enabled { get; set; } = true;
+	/// <summary>
+	/// Gets or sets a value indicating whether this <see cref="Responder"/> can focus.
+	/// </summary>
+	/// <value><c>true</c> if can focus; otherwise, <c>false</c>.</value>
+	public virtual bool CanFocus { get; set; }
 
-		/// <summary>
-		/// Gets or sets a value indicating whether this <see cref="Responder"/> and all its child controls are displayed.
-		/// </summary>
-		public virtual bool Visible { get; set; } = true;
+	/// <summary>
+	/// Gets or sets a value indicating whether this <see cref="Responder"/> has focus.
+	/// </summary>
+	/// <value><c>true</c> if has focus; otherwise, <c>false</c>.</value>
+	public virtual bool HasFocus { get; }
 
-		// Key handling
-		/// <summary>
-		///   This method can be overwritten by view that
-		///     want to provide accelerator functionality
-		///     (Alt-key for example).
-		/// </summary>
-		/// <remarks>
-		///   <para>
-		///     Before keys are sent to the subview on the
-		///     current view, all the views are
-		///     processed and the key is passed to the widgets
-		///     to allow some of them to process the keystroke
-		///     as a hot-key. </para>
-		///  <para>
-		///     For example, if you implement a button that
-		///     has a hotkey ok "o", you would catch the
-		///     combination Alt-o here.  If the event is
-		///     caught, you must return true to stop the
-		///     keystroke from being dispatched to other
-		///     views.
-		///  </para>
-		/// </remarks>
+	/// <summary>
+	/// Gets or sets a value indicating whether this <see cref="Responder"/> can respond to user interaction.
+	/// </summary>
+	public virtual bool Enabled { get; set; } = true;
 
-		public virtual bool ProcessHotKey (KeyEvent kb)
-		{
-			return false;
-		}
+	/// <summary>
+	/// Gets or sets a value indicating whether this <see cref="Responder"/> and all its child controls are displayed.
+	/// </summary>
+	public virtual bool Visible { get; set; } = true;
 
-		/// <summary>
-		///   If the view is focused, gives the view a
-		///   chance to process the keystroke.
-		/// </summary>
-		/// <remarks>
-		///   <para>
-		///     Views can override this method if they are
-		///     interested in processing the given keystroke.
-		///     If they consume the keystroke, they must
-		///     return true to stop the keystroke from being
-		///     processed by other widgets or consumed by the
-		///     widget engine.    If they return false, the
-		///     keystroke will be passed using the ProcessColdKey
-		///     method to other views to process.
-		///   </para>
-		///   <para>
-		///     The View implementation does nothing but return false,
-		///     so it is not necessary to call base.ProcessKey if you
-		///     derive directly from View, but you should if you derive
-		///     other View subclasses.
-		///   </para>
-		/// </remarks>
-		/// <param name="keyEvent">Contains the details about the key that produced the event.</param>
-		public virtual bool ProcessKey (KeyEvent keyEvent)
-		{
-			return false;
-		}
+	/// <summary>
+	/// Method invoked when a mouse event is generated
+	/// </summary>
+	/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
+	/// <param name="mouseEvent">Contains the details about the mouse event.</param>
+	public virtual bool MouseEvent (MouseEvent mouseEvent)
+	{
+		return false;
+	}
 
-		/// <summary>
-		///   This method can be overwritten by views that
-		///     want to provide accelerator functionality
-		///     (Alt-key for example), but without
-		///     interefering with normal ProcessKey behavior.
-		/// </summary>
-		/// <remarks>
-		///   <para>
-		///     After keys are sent to the subviews on the
-		///     current view, all the view are
-		///     processed and the key is passed to the views
-		///     to allow some of them to process the keystroke
-		///     as a cold-key. </para>
-		///  <para>
-		///    This functionality is used, for example, by
-		///    default buttons to act on the enter key.
-		///    Processing this as a hot-key would prevent
-		///    non-default buttons from consuming the enter
-		///    keypress when they have the focus.
-		///  </para>
-		/// </remarks>
-		/// <param name="keyEvent">Contains the details about the key that produced the event.</param>
-		public virtual bool ProcessColdKey (KeyEvent keyEvent)
-		{
-			return false;
-		}
+	/// <summary>
+	/// Called when the mouse first enters the view; the view will now
+	/// receives mouse events until the mouse leaves the view. At which time, <see cref="OnMouseLeave(Gui.MouseEvent)"/>
+	/// will be called.
+	/// </summary>
+	/// <param name="mouseEvent"></param>
+	/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
+	public virtual bool OnMouseEnter (MouseEvent mouseEvent)
+	{
+		return false;
+	}
 
-		/// <summary>
-		/// Method invoked when a key is pressed.
-		/// </summary>
-		/// <param name="keyEvent">Contains the details about the key that produced the event.</param>
-		/// <returns>true if the event was handled</returns>
-		public virtual bool OnKeyDown (KeyEvent keyEvent)
-		{
-			return false;
-		}
+	/// <summary>
+	/// Called when the mouse has moved outside of the view; the view will no longer receive mouse events (until
+	/// the mouse moves within the view again and <see cref="OnMouseEnter(Gui.MouseEvent)"/> is called).
+	/// </summary>
+	/// <param name="mouseEvent"></param>
+	/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
+	public virtual bool OnMouseLeave (MouseEvent mouseEvent)
+	{
+		return false;
+	}
 
-		/// <summary>
-		/// Method invoked when a key is released.
-		/// </summary>
-		/// <param name="keyEvent">Contains the details about the key that produced the event.</param>
-		/// <returns>true if the event was handled</returns>
-		public virtual bool OnKeyUp (KeyEvent keyEvent)
-		{
-			return false;
-		}
+	/// <summary>
+	/// Method invoked when a view gets focus.
+	/// </summary>
+	/// <param name="view">The view that is losing focus.</param>
+	/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
+	public virtual bool OnEnter (View view)
+	{
+		return false;
+	}
 
-		/// <summary>
-		/// Method invoked when a mouse event is generated
-		/// </summary>
-		/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
-		/// <param name="mouseEvent">Contains the details about the mouse event.</param>
-		public virtual bool MouseEvent (MouseEvent mouseEvent)
-		{
-			return false;
-		}
+	/// <summary>
+	/// Method invoked when a view loses focus.
+	/// </summary>
+	/// <param name="view">The view that is getting focus.</param>
+	/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
+	public virtual bool OnLeave (View view)
+	{
+		return false;
+	}
 
-		/// <summary>
-		/// Called when the mouse first enters the view; the view will now
-		/// receives mouse events until the mouse leaves the view. At which time, <see cref="OnMouseLeave(Gui.MouseEvent)"/>
-		/// will be called.
-		/// </summary>
-		/// <param name="mouseEvent"></param>
-		/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
-		public virtual bool OnMouseEnter (MouseEvent mouseEvent)
-		{
-			return false;
-		}
+	/// <summary>
+	/// Method invoked when the <see cref="CanFocus"/> property from a view is changed.
+	/// </summary>
+	public virtual void OnCanFocusChanged () { }
 
-		/// <summary>
-		/// Called when the mouse has moved outside of the view; the view will no longer receive mouse events (until
-		/// the mouse moves within the view again and <see cref="OnMouseEnter(Gui.MouseEvent)"/> is called).
-		/// </summary>
-		/// <param name="mouseEvent"></param>
-		/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
-		public virtual bool OnMouseLeave (MouseEvent mouseEvent)
-		{
-			return false;
-		}
+	/// <summary>
+	/// Method invoked when the <see cref="Enabled"/> property from a view is changed.
+	/// </summary>
+	public virtual void OnEnabledChanged () { }
 
-		/// <summary>
-		/// Method invoked when a view gets focus.
-		/// </summary>
-		/// <param name="view">The view that is losing focus.</param>
-		/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
-		public virtual bool OnEnter (View view)
-		{
-			return false;
-		}
+	/// <summary>
+	/// Method invoked when the <see cref="Visible"/> property from a view is changed.
+	/// </summary>
+	public virtual void OnVisibleChanged () { }
 
-		/// <summary>
-		/// Method invoked when a view loses focus.
-		/// </summary>
-		/// <param name="view">The view that is getting focus.</param>
-		/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
-		public virtual bool OnLeave (View view)
-		{
+	// TODO: v2 - nuke this
+	/// <summary>
+	/// Utilty function to determine <paramref name="method"/> is overridden in the <paramref name="subclass"/>.
+	/// </summary>
+	/// <param name="subclass">The view.</param>
+	/// <param name="method">The method name.</param>
+	/// <returns><see langword="true"/> if it's overridden, <see langword="false"/> otherwise.</returns>
+	internal static bool IsOverridden (Responder subclass, string method)
+	{
+		MethodInfo m = subclass.GetType ().GetMethod (method,
+			BindingFlags.Instance
+			| BindingFlags.Public
+			| BindingFlags.NonPublic
+			| BindingFlags.DeclaredOnly);
+		if (m == null) {
 			return false;
 		}
+		return m.GetBaseDefinition ().DeclaringType != m.DeclaringType;
+	}
 
-		/// <summary>
-		/// Method invoked when the <see cref="CanFocus"/> property from a view is changed.
-		/// </summary>
-		public virtual void OnCanFocusChanged () { }
-
-		/// <summary>
-		/// Method invoked when the <see cref="Enabled"/> property from a view is changed.
-		/// </summary>
-		public virtual void OnEnabledChanged () { }
-
-		/// <summary>
-		/// Method invoked when the <see cref="Visible"/> property from a view is changed.
-		/// </summary>
-		public virtual void OnVisibleChanged () { }
-
-		// TODO: v2 - nuke this
-		/// <summary>
-		/// Utilty function to determine <paramref name="method"/> is overridden in the <paramref name="subclass"/>.
-		/// </summary>
-		/// <param name="subclass">The view.</param>
-		/// <param name="method">The method name.</param>
-		/// <returns><see langword="true"/> if it's overridden, <see langword="false"/> otherwise.</returns>
-		internal static bool IsOverridden (Responder subclass, string method)
-		{
-			MethodInfo m = subclass.GetType ().GetMethod (method,
-				BindingFlags.Instance
-				| BindingFlags.Public
-				| BindingFlags.NonPublic
-				| BindingFlags.DeclaredOnly);
-			if (m == null) {
-				return false;
+	/// <summary>
+	/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+	/// </summary>
+	/// <remarks>
+	/// If disposing equals true, the method has been called directly
+	/// or indirectly by a user's code. Managed and unmanaged resources
+	/// can be disposed.
+	/// If disposing equals false, the method has been called by the
+	/// runtime from inside the finalizer and you should not reference
+	/// other objects. Only unmanaged resources can be disposed.
+	/// </remarks>
+	/// <param name="disposing"></param>
+	protected virtual void Dispose (bool disposing)
+	{
+		if (!disposedValue) {
+			if (disposing) {
+				// TODO: dispose managed state (managed objects)
 			}
-			return m.GetBaseDefinition ().DeclaringType != m.DeclaringType;
-		}
-
-		/// <summary>
-		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
-		/// </summary>
-		/// <remarks>
-		/// If disposing equals true, the method has been called directly
-		/// or indirectly by a user's code. Managed and unmanaged resources
-		/// can be disposed.
-		/// If disposing equals false, the method has been called by the
-		/// runtime from inside the finalizer and you should not reference
-		/// other objects. Only unmanaged resources can be disposed.
-		/// </remarks>
-		/// <param name="disposing"></param>
-		protected virtual void Dispose (bool disposing)
-		{
-			if (!disposedValue) {
-				if (disposing) {
-					// TODO: dispose managed state (managed objects)
-				}
 
-				disposedValue = true;
-			}
+			disposedValue = true;
 		}
+	}
 
-		/// <summary>
-		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resource.
-		/// </summary>
-		public void Dispose ()
-		{
-			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
-			Dispose (disposing: true);
-			GC.SuppressFinalize (this);
+	/// <summary>
+	/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resource.
+	/// </summary>
+	public void Dispose ()
+	{
+		// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+		Dispose (disposing: true);
+		GC.SuppressFinalize (this);
 #if DEBUG_IDISPOSABLE
-			WasDisposed = true;
+		WasDisposed = true;
 
-			foreach (var instance in Instances.Where (x => x.WasDisposed).ToList ()) {
-				Instances.Remove (instance);
-			}
-#endif
+		foreach (var instance in Instances.Where (x => x.WasDisposed).ToList ()) {
+			Instances.Remove (instance);
 		}
+#endif
 	}
 }

+ 119 - 237
Terminal.Gui/Input/ShortcutHelper.cs

@@ -1,270 +1,152 @@
 using System;
-using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
 using System.Text;
-using System.Threading.Tasks;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui;
+/// <summary>
+/// Represents a helper to manipulate shortcut keys used on views.
+/// </summary>
+public class ShortcutHelper {
+	// TODO: Update this to use Key, not KeyCode
+	private KeyCode shortcut;
+
 	/// <summary>
-	/// Represents a helper to manipulate shortcut keys used on views.
+	/// This is the global setting that can be used as a global shortcut to invoke the action on the view.
 	/// </summary>
-	public class ShortcutHelper {
-		private Key shortcut;
-
-		/// <summary>
-		/// This is the global setting that can be used as a global shortcut to invoke the action on the view.
-		/// </summary>
-		public virtual Key Shortcut {
-			get => shortcut;
-			set {
-				if (shortcut != value && (PostShortcutValidation (value) || value == Key.Null)) {
-					shortcut = value;
-				}
+	public virtual KeyCode Shortcut {
+		get => shortcut;
+		set {
+			if (shortcut != value && (PostShortcutValidation (value) || value is KeyCode.Null or KeyCode.Unknown)) {
+				shortcut = value;
 			}
 		}
+	}
 
-		/// <summary>
-		/// The keystroke combination used in the <see cref="Shortcut"/> as string.
-		/// </summary>
-		public virtual string ShortcutTag => GetShortcutTag (shortcut);
-
-		/// <summary>
-		/// The action to run if the <see cref="Shortcut"/> is defined.
-		/// </summary>
-		public virtual Action ShortcutAction { get; set; }
-
-		/// <summary>
-		/// Gets the key with all the keys modifiers, especially the shift key that sometimes have to be injected later.
-		/// </summary>
-		/// <param name="kb">The <see cref="KeyEvent"/> to check.</param>
-		/// <returns>The <see cref="KeyEvent.Key"/> with all the keys modifiers.</returns>
-		public static Key GetModifiersKey (KeyEvent kb)
-		{
-			var key = kb.Key;
-			if (kb.IsAlt && (key & Key.AltMask) == 0) {
-				key |= Key.AltMask;
-			}
-			if (kb.IsCtrl && (key & Key.CtrlMask) == 0) {
-				key |= Key.CtrlMask;
-			}
-			if (kb.IsShift && (key & Key.ShiftMask) == 0) {
-				key |= Key.ShiftMask;
-			}
-
-			return key;
+	/// <summary>
+	/// The keystroke combination used in the <see cref="Shortcut"/> as string.
+	/// </summary>
+	public virtual string ShortcutTag => Key.ToString (shortcut, MenuBar.ShortcutDelimiter);
+	
+	/// <summary>
+	/// Return key as string.
+	/// </summary>
+	/// <param name="key">The key to extract.</param>
+	/// <param name="knm">Correspond to the non modifier key.</param>
+	static string GetKeyToString (KeyCode key, out KeyCode knm)
+	{
+		if (key == KeyCode.Null) {
+			knm = KeyCode.Null;
+			return "";
 		}
 
-		/// <summary>
-		/// Get the <see cref="Shortcut"/> key as string.
-		/// </summary>
-		/// <param name="shortcut">The shortcut key.</param>
-		/// <param name="delimiter">The delimiter string.</param>
-		/// <returns></returns>
-		public static string GetShortcutTag (Key shortcut, string delimiter = null)
-		{
-			if (shortcut == Key.Null) {
-				return "";
-			}
-
-			var k = shortcut;
-			if (delimiter == null) {
-				delimiter = MenuBar.ShortcutDelimiter;
-			}
-			string tag = string.Empty;
-			var sCut = GetKeyToString (k, out Key knm).ToString ();
-			if (knm == Key.Unknown) {
-				k &= ~Key.Unknown;
-				sCut = GetKeyToString (k, out _).ToString ();
-			}
-			if ((k & Key.CtrlMask) != 0) {
-				tag = "Ctrl";
+		knm = key;
+		var mK = key & (KeyCode.AltMask | KeyCode.CtrlMask | KeyCode.ShiftMask);
+		knm &= ~mK;
+		for (uint i = (uint)KeyCode.F1; i < (uint)KeyCode.F12; i++) {
+			if (knm == (KeyCode)i) {
+				mK |= (KeyCode)i;
 			}
-			if ((k & Key.ShiftMask) != 0) {
-				if (!string.IsNullOrEmpty(tag)) {
-					tag += delimiter;
-				}
-				tag += "Shift";
-			}
-			if ((k & Key.AltMask) != 0) {
-				if (!string.IsNullOrEmpty(tag)) {
-					tag += delimiter;
-				}
-				tag += "Alt";
-			}
-
-			string [] keys = sCut.Split (",");
-			for (int i = 0; i < keys.Length; i++) {
-				var key = keys [i].Trim ();
-				if (key == Key.AltMask.ToString () || key == Key.ShiftMask.ToString () || key == Key.CtrlMask.ToString ()) {
-					continue;
-				}
-				if (!string.IsNullOrEmpty(tag)) {
-					tag += delimiter;
-				}
-				if (!key.Contains ("F") && key.Length > 2 && keys.Length == 1) {
-					k = (uint)Key.AltMask + k;
-					tag += ((char)k).ToString ();
-				} else if (key.Length == 2 && key.StartsWith ("D")) {
-					tag += ((char)key.ElementAt (1)).ToString ();
-				} else {
-					tag += key;
-				}
-			}
-
-			return tag;
 		}
-
-		/// <summary>
-		/// Return key as string.
-		/// </summary>
-		/// <param name="key">The key to extract.</param>
-		/// <param name="knm">Correspond to the non modifier key.</param>
-		public static string GetKeyToString (Key key, out Key knm)
-		{
-			if (key == Key.Null) {
-				knm = Key.Null;
-				return "";
-			}
-
-			knm = key;
-			var mK = key & (Key.AltMask | Key.CtrlMask | Key.ShiftMask);
-			knm &= ~mK;
-			for (uint i = (uint)Key.F1; i < (uint)Key.F12; i++) {
-				if (knm == (Key)i) {
-					mK |= (Key)i;
-				}
-			}
-			knm &= ~mK;
-			uint.TryParse (knm.ToString (), out uint c);
-			var s = mK == Key.Null ? "" : mK.ToString ();
-			if (s != "" && (knm != Key.Null || c > 0)) {
-				s += ",";
-			}
-			s += c == 0 ? knm == Key.Null ? "" : knm.ToString () : ((char)c).ToString ();
-			return s;
+		knm &= ~mK;
+		uint.TryParse (knm.ToString (), out uint c);
+		var s = mK == KeyCode.Null ? "" : mK.ToString ();
+		if (s != "" && (knm != KeyCode.Null || c > 0)) {
+			s += ",";
 		}
+		s += c == 0 ? knm == KeyCode.Null ? "" : knm.ToString () : ((char)c).ToString ();
+		return s;
+	}
 
-		/// <summary>
-		/// Allows to retrieve a <see cref="Key"/> from a <see cref="ShortcutTag"/>
-		/// </summary>
-		/// <param name="tag">The key as string.</param>
-		/// <param name="delimiter">The delimiter string.</param>
-		public static Key GetShortcutFromTag (string tag, string delimiter = null)
-		{
-			var sCut = tag;
-			if (string.IsNullOrEmpty(sCut)) {
-				return default;
-			}
+	/// <summary>
+	/// Allows to retrieve a <see cref="KeyCode"/> from a <see cref="ShortcutTag"/>
+	/// </summary>
+	/// <param name="tag">The key as string.</param>
+	/// <param name="delimiter">The delimiter string.</param>
+	public static KeyCode GetShortcutFromTag (string tag, Rune delimiter = default)
+	{
+		var sCut = tag;
+		if (string.IsNullOrEmpty (sCut)) {
+			return default;
+		}
 
-			Key key = Key.Null;
-			//var hasCtrl = false;
-			if (delimiter == null) {
-				delimiter = MenuBar.ShortcutDelimiter;
-			}
+		KeyCode key = KeyCode.Null;
+		//var hasCtrl = false;
+		if (delimiter == default) {
+			delimiter = MenuBar.ShortcutDelimiter;
+		}
 
-			string [] keys = sCut.Split (delimiter);
-			for (int i = 0; i < keys.Length; i++) {
-				var k = keys [i];
-				if (k == "Ctrl") {
-					//hasCtrl = true;
-					key |= Key.CtrlMask;
-				} else if (k == "Shift") {
-					key |= Key.ShiftMask;
-				} else if (k == "Alt") {
-					key |= Key.AltMask;
-				} else if (k.StartsWith ("F") && k.Length > 1) {
-					int.TryParse (k.Substring (1).ToString (), out int n);
-					for (uint j = (uint)Key.F1; j <= (uint)Key.F12; j++) {
-						int.TryParse (((Key)j).ToString ().Substring (1), out int f);
-						if (f == n) {
-							key |= (Key)j;
-						}
+		string [] keys = sCut.Split (delimiter.ToString());
+		for (int i = 0; i < keys.Length; i++) {
+			var k = keys [i];
+			if (k == "Ctrl") {
+				//hasCtrl = true;
+				key |= KeyCode.CtrlMask;
+			} else if (k == "Shift") {
+				key |= KeyCode.ShiftMask;
+			} else if (k == "Alt") {
+				key |= KeyCode.AltMask;
+			} else if (k.StartsWith ("F") && k.Length > 1) {
+				int.TryParse (k.Substring (1).ToString (), out int n);
+				for (uint j = (uint)KeyCode.F1; j <= (uint)KeyCode.F12; j++) {
+					int.TryParse (((KeyCode)j).ToString ().Substring (1), out int f);
+					if (f == n) {
+						key |= (KeyCode)j;
 					}
-				} else {
-					key |= (Key)Enum.Parse (typeof (Key), k.ToString ());
 				}
+			} else {
+				key |= (KeyCode)Enum.Parse (typeof (KeyCode), k.ToString ());
 			}
-
-			return key;
 		}
 
-		/// <summary>
-		/// Lookup for a <see cref="Key"/> on range of keys.
-		/// </summary>
-		/// <param name="key">The source key.</param>
-		/// <param name="first">First key in range.</param>
-		/// <param name="last">Last key in range.</param>
-		public static bool CheckKeysFlagRange (Key key, Key first, Key last)
-		{
-			for (uint i = (uint)first; i < (uint)last; i++) {
-				if ((key | (Key)i) == key) {
-					return true;
-				}
-			}
-			return false;
-		}
+		return key;
+	}
 
-		/// <summary>
-		/// Used at key down or key press validation.
-		/// </summary>
-		/// <param name="key">The key to validate.</param>
-		/// <returns><c>true</c> if is valid.<c>false</c>otherwise.</returns>
-		public static bool PreShortcutValidation (Key key)
-		{
-			if ((key & (Key.CtrlMask | Key.ShiftMask | Key.AltMask)) == 0 && !CheckKeysFlagRange (key, Key.F1, Key.F12)) {
-				return false;
+	/// <summary>
+	/// Lookup for a <see cref="KeyCode"/> on range of keys.
+	/// </summary>
+	/// <param name="key">The source key.</param>
+	/// <param name="first">First key in range.</param>
+	/// <param name="last">Last key in range.</param>
+	public static bool CheckKeysFlagRange (KeyCode key, KeyCode first, KeyCode last)
+	{
+		for (uint i = (uint)first; i < (uint)last; i++) {
+			if ((key | (KeyCode)i) == key) {
+				return true;
 			}
-
-			return true;
 		}
+		return false;
+	}
 
-		/// <summary>
-		/// Used at key up validation.
-		/// </summary>
-		/// <param name="key">The key to validate.</param>
-		/// <returns><c>true</c> if is valid.<c>false</c>otherwise.</returns>
-		public static bool PostShortcutValidation (Key key)
-		{
-			GetKeyToString (key, out Key knm);
-
-			if (CheckKeysFlagRange (key, Key.F1, Key.F12) ||
-				((key & (Key.CtrlMask | Key.ShiftMask | Key.AltMask)) != 0 && knm != Key.Null && knm != Key.Unknown)) {
-				return true;
-			}
+	/// <summary>
+	/// Used at key down or key press validation.
+	/// </summary>
+	/// <param name="key">The key to validate.</param>
+	/// <returns><c>true</c> if is valid.<c>false</c>otherwise.</returns>
+	public static bool PreShortcutValidation (KeyCode key)
+	{
+		if ((key & (KeyCode.CtrlMask | KeyCode.ShiftMask | KeyCode.AltMask)) == 0 && !CheckKeysFlagRange (key, KeyCode.F1, KeyCode.F12)) {
 			return false;
 		}
 
-		/// <summary>
-		/// Allows a view to run a <see cref="View.ShortcutAction"/> if defined.
-		/// </summary>
-		/// <param name="kb">The <see cref="KeyEvent"/></param>
-		/// <param name="view">The <see cref="View"/></param>
-		/// <returns><c>true</c> if defined <c>false</c>otherwise.</returns>
-		public static bool FindAndOpenByShortcut (KeyEvent kb, View view = null)
-		{
-			if (view == null) {
-				return false;			}
-
-			var key = kb.KeyValue;
-			var keys = GetModifiersKey (kb);
-			key |= (int)keys;
-			foreach (var v in view.Subviews) {
-				if (v.Shortcut != Key.Null && v.Shortcut == (Key)key) {
-					var action = v.ShortcutAction;
-					if (action != null) {
-						Application.MainLoop.AddIdle (() => {
-							action ();
-							return false;
-						});
-					}
-					return true;
-				}
-				if (FindAndOpenByShortcut (kb, v)) {
-					return true;
-				}
-			}
+		return true;
+	}
 
-			return false;
+	/// <summary>
+	/// Used at key up validation.
+	/// </summary>
+	/// <param name="key">The key to validate.</param>
+	/// <returns><c>true</c> if is valid.<c>false</c>otherwise.</returns>
+	public static bool PostShortcutValidation (KeyCode key)
+	{
+		GetKeyToString (key, out KeyCode knm);
+
+		if (CheckKeysFlagRange (key, KeyCode.F1, KeyCode.F12) ||
+			((key & (KeyCode.CtrlMask | KeyCode.ShiftMask | KeyCode.AltMask)) != 0 && knm != KeyCode.Null && knm != KeyCode.Unknown)) {
+			return true;
 		}
+		Debug.WriteLine ($"WARNING: {Key.ToString (key)} is not a valid shortcut key.");
+		return false;
 	}
 }
+

+ 2 - 2
Terminal.Gui/MainLoop.cs

@@ -10,7 +10,7 @@ using System.Collections.ObjectModel;
 
 namespace Terminal.Gui {
 	/// <summary>
-	/// Public interface to create a platform specific <see cref="MainLoop"/> driver.
+	/// Interface to create a platform specific <see cref="MainLoop"/> driver.
 	/// </summary>
 	internal interface IMainLoopDriver {
 		/// <summary>
@@ -295,7 +295,7 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Used for unit tests.
 		/// </summary>
-		internal bool Running { get; private set; }
+		internal bool Running { get; set; }
 
 		/// <summary>
 		///   Determines whether there are pending events to be processed.

+ 3 - 12
Terminal.Gui/Resources/config.json

@@ -17,22 +17,13 @@
   "ConfigurationManager.ThrowOnJsonErrors": false,
 
   "Application.AlternateBackwardKey": {
-    "Key": "PageUp",
-    "Modifiers": [
-      "Ctrl"
-    ]
+    "Key": "Ctrl+PageUp"
   },
   "Application.AlternateForwardKey": {
-    "Key": "PageDown",
-    "Modifiers": [
-      "Ctrl"
-    ]
+    "Key": "Ctrl+PageDown"
   },
   "Application.QuitKey": {
-    "Key": "Q",
-    "Modifiers": [
-      "Ctrl"
-    ]
+    "Key": "Ctrl+Q"
   },
   "Application.IsMouseDisabled": false,
   "Theme": "Default",

+ 3 - 4
Terminal.Gui/Terminal.Gui.csproj

@@ -19,8 +19,8 @@
     <DebugType>portable</DebugType>
   </PropertyGroup>
   <PropertyGroup>
-    <TargetFrameworks>net7.0</TargetFrameworks>
-    <LangVersion>11.0</LangVersion>
+    <TargetFrameworks>net8.0</TargetFrameworks>
+    <!--<LangVersion>11.0</LangVersion>-->
     <RootNamespace>Terminal.Gui</RootNamespace>
     <AssemblyName>Terminal.Gui</AssemblyName>
     <SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage>
@@ -38,9 +38,8 @@
   <!-- Dependencies -->
   <!-- =================================================================== -->
   <ItemGroup>
-    <PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
     <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="all" />
-    <PackageReference Include="System.IO.Abstractions" Version="19.2.87" />
+    <PackageReference Include="System.IO.Abstractions" Version="20.0.4" />
     <PackageReference Include="System.Text.Json" Version="8.0.0" />
     <PackageReference Include="System.Management" Version="8.0.0" />
     <PackageReference Include="Wcwidth" Version="2.0.0" />

+ 6 - 6
Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs

@@ -29,7 +29,7 @@ namespace Terminal.Gui {
 		public AppendAutocomplete (TextField textField)
 		{
 			this.textField = textField;
-			SelectionKey = Key.Tab;
+			SelectionKey = KeyCode.Tab;
 
 			ColorScheme = new ColorScheme {
 				Normal = new Attribute (Color.DarkGray, Color.Black),
@@ -54,16 +54,16 @@ namespace Terminal.Gui {
 		}
 
 		/// <inheritdoc/>
-		public override bool ProcessKey (KeyEvent kb)
+		public override bool ProcessKey (Key a)
 		{
-			var key = kb.Key;
+			var key = a.KeyCode;
 			if (key == SelectionKey) {
 				return this.AcceptSelectionIfAny ();
 			} else
-			if (key == Key.CursorUp) {
+			if (key == KeyCode.CursorUp) {
 				return this.CycleSuggestion (1);
 			} else
-			if (key == Key.CursorDown) {
+			if (key == KeyCode.CursorDown) {
 				return this.CycleSuggestion (-1);
 			} else if (key == CloseKey && Suggestions.Any ()) {
 				ClearSuggestions ();
@@ -71,7 +71,7 @@ namespace Terminal.Gui {
 				return true;
 			}
 
-			if (char.IsLetterOrDigit ((char)kb.KeyValue)) {
+			if (char.IsLetterOrDigit ((char)a)) {
 				_suspendSuggestions = false;
 			}
 

+ 7 - 4
Terminal.Gui/Text/Autocomplete/AutocompleteBase.cs

@@ -39,14 +39,17 @@ namespace Terminal.Gui {
 		/// <inheritdoc/>
 		public abstract ColorScheme ColorScheme { get; set; }
 
+		// TODO: Update to use Key instead of KeyCode
 		/// <inheritdoc/>
-		public virtual Key SelectionKey { get; set; } = Key.Enter;
+		public virtual KeyCode SelectionKey { get; set; } = KeyCode.Enter;
 
+		// TODO: Update to use Key instead of KeyCode
 		/// <inheritdoc/>
-		public virtual Key CloseKey { get; set; } = Key.Esc;
+		public virtual KeyCode CloseKey { get; set; } = KeyCode.Esc;
 
+		// TODO: Update to use Key instead of KeyCode
 		/// <inheritdoc/>
-		public virtual Key Reopen { get; set; } = Key.Space | Key.CtrlMask | Key.AltMask;
+		public virtual KeyCode Reopen { get; set; } = KeyCode.Space | KeyCode.CtrlMask | KeyCode.AltMask;
 
 		/// <inheritdoc/>
 		public virtual AutocompleteContext Context { get; set; }
@@ -55,7 +58,7 @@ namespace Terminal.Gui {
 		public abstract bool MouseEvent (MouseEvent me, bool fromHost = false);
 
 		/// <inheritdoc/>
-		public abstract bool ProcessKey (KeyEvent kb);
+		public abstract bool ProcessKey (Key a);
 		/// <inheritdoc/>
 		public abstract void RenderOverlay (Point renderAt);
 

+ 8 - 5
Terminal.Gui/Text/Autocomplete/IAutocomplete.cs

@@ -53,20 +53,23 @@ namespace Terminal.Gui {
 		/// </summary>
 		ColorScheme ColorScheme { get; set; }
 
+		// TODO: Update to use Key instead of KeyCode
 		/// <summary>
 		/// The key that the user must press to accept the currently selected autocomplete suggestion
 		/// </summary>
-		Key SelectionKey { get; set; }
+		KeyCode SelectionKey { get; set; }
 
+		// TODO: Update to use Key instead of KeyCode
 		/// <summary>
 		/// The key that the user can press to close the currently popped autocomplete menu
 		/// </summary>
-		Key CloseKey { get; set; }
+		KeyCode CloseKey { get; set; }
 
+		// TODO: Update to use Key instead of KeyCode
 		/// <summary>
 		/// The key that the user can press to reopen the currently popped autocomplete menu
 		/// </summary>
-		Key Reopen { get; set; }
+		KeyCode Reopen { get; set; }
 
 		/// <summary>
 		/// The context used by the autocomplete menu.
@@ -85,9 +88,9 @@ namespace Terminal.Gui {
 		/// up/down apply to the autocomplete control instead of changing the cursor position in
 		/// the underlying text view.
 		/// </summary>
-		/// <param name="kb">The key event.</param>
+		/// <param name="a">The key event.</param>
 		/// <returns><c>true</c>if the key can be handled <c>false</c>otherwise.</returns>
-		bool ProcessKey (KeyEvent kb);
+		bool ProcessKey (Key a);
 
 		/// <summary>
 		/// Handle mouse events before <see cref="HostControl"/> e.g. to make mouse events like

+ 11 - 11
Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs

@@ -152,7 +152,7 @@ namespace Terminal.Gui {
 		public override void RenderOverlay (Point renderAt)
 		{
 			if (!Context.Canceled && Suggestions.Count > 0 && !Visible && HostControl?.HasFocus == true) {
-				ProcessKey (new KeyEvent ((Key)(Suggestions [0].Title [0]), new KeyModifiers ()));
+				ProcessKey (new ((KeyCode)(Suggestions [0].Title [0])));
 			} else if (!Visible || HostControl?.HasFocus == false || Suggestions.Count == 0) {
 				LastPopupPos = null;
 				Visible = false;
@@ -276,18 +276,18 @@ namespace Terminal.Gui {
 		/// up/down apply to the autocomplete control instead of changing the cursor position in
 		/// the underlying text view.
 		/// </summary>
-		/// <param name="kb">The key event.</param>
+		/// <param name="a">The key event.</param>
 		/// <returns><c>true</c>if the key can be handled <c>false</c>otherwise.</returns>
-		public override bool ProcessKey (KeyEvent kb)
+		public override bool ProcessKey (Key a)
 		{
-			if (SuggestionGenerator.IsWordChar ((Rune)(char)kb.Key)) {
+			if (SuggestionGenerator.IsWordChar ((Rune)a)) {
 				Visible = true;
 				ManipulatePopup ();
 				closed = false;
 				return false;
 			}
 
-			if (kb.Key == Reopen) {
+			if (a.KeyCode == Reopen) {
 				Context.Canceled = false;
 				return ReopenSuggestions ();
 			}
@@ -300,19 +300,19 @@ namespace Terminal.Gui {
 				return false;
 			}
 
-			if (kb.Key == Key.CursorDown) {
+			if (a.KeyCode == KeyCode.CursorDown) {
 				MoveDown ();
 				return true;
 			}
 
-			if (kb.Key == Key.CursorUp) {
+			if (a.KeyCode == KeyCode.CursorUp) {
 				MoveUp ();
 				return true;
 			}
 
 			// TODO : Revisit this
-			/*if (kb.Key == Key.CursorLeft || kb.Key == Key.CursorRight) {
-				GenerateSuggestions (kb.Key == Key.CursorLeft ? -1 : 1);
+			/*if (a.ConsoleDriverKey == Key.CursorLeft || a.ConsoleDriverKey == Key.CursorRight) {
+				GenerateSuggestions (a.ConsoleDriverKey == Key.CursorLeft ? -1 : 1);
 				if (Suggestions.Count == 0) {
 					Visible = false;
 					if (!closed) {
@@ -322,11 +322,11 @@ namespace Terminal.Gui {
 				return false;
 			}*/
 
-			if (kb.Key == SelectionKey) {
+			if (a.KeyCode == SelectionKey) {
 				return Select ();
 			}
 
-			if (kb.Key == CloseKey) {
+			if (a.KeyCode == CloseKey) {
 				Close ();
 				Context.Canceled = true;
 				return true;

+ 4 - 4
Terminal.Gui/Text/CollectionNavigatorBase.cs

@@ -203,15 +203,15 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Returns true if <paramref name="kb"/> is a searchable key
+		/// Returns true if <paramref name="a"/> is a searchable key
 		/// (e.g. letters, numbers, etc) that are valid to pass to this
 		/// class for search filtering.
 		/// </summary>
-		/// <param name="kb"></param>
+		/// <param name="a"></param>
 		/// <returns></returns>
-		public static bool IsCompatibleKey (KeyEvent kb)
+		public static bool IsCompatibleKey (Key a)
 		{
-			return !kb.IsAlt && !kb.IsCtrl;
+			return !a.IsAlt && !a.IsCtrl;
 		}
 	}
 }

+ 19 - 15
Terminal.Gui/Text/TextFormatter.cs

@@ -929,20 +929,20 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Finds the hotkey and its location in text. 
+		/// Finds the HotKey and its location in text. 
 		/// </summary>
 		/// <param name="text">The text to look in.</param>
-		/// <param name="hotKeySpecifier">The hotkey specifier (e.g. '_') to look for.</param>
-		/// <param name="firstUpperCase">If <c>true</c> the legacy behavior of identifying the first upper case character as the hotkey will be enabled.
+		/// <param name="hotKeySpecifier">The HotKey specifier (e.g. '_') to look for.</param>
+		/// <param name="firstUpperCase">If <c>true</c> the legacy behavior of identifying the first upper case character as the HotKey will be enabled.
 		/// Regardless of the value of this parameter, <c>hotKeySpecifier</c> takes precedence.</param>
 		/// <param name="hotPos">Outputs the Rune index into <c>text</c>.</param>
-		/// <param name="hotKey">Outputs the hotKey.</param>
-		/// <returns><c>true</c> if a hotkey was found; <c>false</c> otherwise.</returns>
+		/// <param name="hotKey">Outputs the hotKey. <see cref="Key.Empty"/> if not found.</param>
+		/// <returns><c>true</c> if a HotKey was found; <c>false</c> otherwise.</returns>
 		public static bool FindHotKey (string text, Rune hotKeySpecifier, bool firstUpperCase, out int hotPos, out Key hotKey)
 		{
 			if (string.IsNullOrEmpty (text) || hotKeySpecifier == (Rune)0xFFFF) {
 				hotPos = -1;
-				hotKey = Key.Unknown;
+				hotKey = KeyCode.Null;
 				return false;
 			}
 
@@ -983,14 +983,18 @@ namespace Terminal.Gui {
 			if (hot_key != (Rune)0 && hot_pos != -1) {
 				hotPos = hot_pos;
 
-				if (Rune.IsValid (hot_key.Value) && char.IsLetterOrDigit ((char)hot_key.Value)) {
-					hotKey = (Key)char.ToUpperInvariant ((char)hot_key.Value);
+				var newHotKey = (KeyCode)hot_key.Value;
+				if (newHotKey != KeyCode.Unknown && newHotKey != KeyCode.Null && !(newHotKey == KeyCode.Space || Rune.IsControl (hot_key))) {
+					if ((newHotKey & ~KeyCode.Space) is >= KeyCode.A and <= KeyCode.Z) {
+						newHotKey &= ~KeyCode.Space;
+					}
+					hotKey = newHotKey;
 					return true;
 				}
 			}
 
 			hotPos = -1;
-			hotKey = Key.Unknown;
+			hotKey = KeyCode.Null;
 			return false;
 		}
 
@@ -1047,7 +1051,7 @@ namespace Terminal.Gui {
 		TextAlignment _textAlignment;
 		VerticalTextAlignment _textVerticalAlignment;
 		TextDirection _textDirection;
-		Key _hotKey;
+		Key _hotKey = new Key ();
 		int _hotKeyPos = -1;
 		Size _size;
 		private bool _autoSize;
@@ -1225,17 +1229,17 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// The specifier character for the hotkey (e.g. '_'). Set to '\xffff' to disable hotkey support for this View instance. The default is '\xffff'.
+		/// The specifier character for the hot key (e.g. '_'). Set to '\xffff' to disable hot key support for this View instance. The default is '\xffff'.
 		/// </summary>
 		public Rune HotKeySpecifier { get; set; } = (Rune)0xFFFF;
 
 		/// <summary>
-		/// The position in the text of the hotkey. The hotkey will be rendered using the hot color.
+		/// The position in the text of the hot key. The hot key will be rendered using the hot color.
 		/// </summary>
 		public int HotKeyPos { get => _hotKeyPos; internal set => _hotKeyPos = value; }
 
 		/// <summary>
-		/// Gets the hotkey. Will be an upper case letter or digit.
+		/// Gets or sets the hot key. Must be be an upper case letter or digit. Fires the <see cref="HotKeyChanged"/> event.
 		/// </summary>
 		public Key HotKey {
 			get => _hotKey;
@@ -1287,10 +1291,10 @@ namespace Terminal.Gui {
 					NeedsFormat = false;
 					return _lines;
 				}
-
+				
 				if (NeedsFormat) {
 					var shown_text = _text;
-					if (FindHotKey (_text, HotKeySpecifier, true, out _hotKeyPos, out Key newHotKey)) {
+					if (FindHotKey (_text, HotKeySpecifier, true, out _hotKeyPos, out var newHotKey)) {
 						HotKey = newHotKey;
 						shown_text = RemoveHotKeySpecifier (Text, _hotKeyPos, HotKeySpecifier);
 						shown_text = ReplaceHotKeyWithTag (shown_text, _hotKeyPos);

+ 2 - 2
Terminal.Gui/Text/ViewLayout.cs

@@ -534,7 +534,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Removes the <see cref="SetNeedsLayout"/> setting on this view.
+		/// Indicates that the view does not need to be laid out.
 		/// </summary>
 		protected void ClearLayoutNeeded ()
 		{
@@ -947,7 +947,7 @@ namespace Terminal.Gui {
 		/// response to the container view or terminal resizing.
 		/// </summary>
 		/// <remarks>
-		/// Calls <see cref="OnLayoutComplete"/> (which raises the <see cref="LayoutComplete"/> event) before it returns.
+		/// Raises the <see cref="LayoutComplete"/> event) before it returns.
 		/// </remarks>
 		public virtual void LayoutSubviews ()
 		{

+ 20 - 15
Terminal.Gui/View/View.cs

@@ -8,22 +8,24 @@ namespace Terminal.Gui {
 	#region API Docs
 	/// <summary>
 	/// View is the base class for all views on the screen and represents a visible element that can render itself and 
-	/// contains zero or more nested views, called SubViews.
+	/// contains zero or more nested views, called SubViews. View provides basic functionality for layout, positioning,
+	/// and drawing. In addition, View provides keyboard and mouse event handling.
 	/// </summary>
 	/// <remarks>
+	/// <list type="table">
+	///	<listheader>
+	///	<term>Term</term><description>Definition</description>
+	///	</listheader>
+	///	<item>
+	///	<term>SubView</term><description>A View that is contained in another view and will be rendered as part of the containing view's ContentArea. 
+	/// SubViews are added to another view via the <see cref="View.Add(View)"/>` method. A View may only be a SubView of a single View. </description>
+	///	</item>
+	///	<item>
+	///		<term>SuperView</term><description>The View that is a container for SubViews.</description>
+	///	</item>
+	/// </list>
 	/// <para>
-	///    The View defines the base functionality for user interface elements in Terminal.Gui. Views
-	///    can contain one or more subviews, can respond to user input and render themselves on the screen.
-	/// </para>
-	/// <para>
-	/// SubView - A View that is contained in another view and will be rendered as part of the containing view's ContentArea. 
-	/// SubViews are added to another view via the <see cref="View.Add(View)"/>` method. A View may only be a SubView of a single View. 
-	/// </para>
-	/// <para>
-	/// SuperView - The View that is a container for SubViews. 
-	/// </para>
-	/// <para>
-	/// Focus is a concept that is used to describe which Responder is currently receiving user input. Only views that are
+	/// Focus is a concept that is used to describe which View is currently receiving user input. Only Views that are
 	/// <see cref="Enabled"/>, <see cref="Visible"/>, and <see cref="CanFocus"/> will receive focus.
 	/// </para>
 	/// <para>
@@ -110,7 +112,9 @@ namespace Terminal.Gui {
 	///     to override base class layout code optimally by doing so only on first run,
 	///     instead of on every run.
 	///   </para>
-	/// </remarks>
+	/// <para>
+	///	See <see href="../docs/keyboard.md">for an overview of View keyboard handling.</see>
+	/// </para>	/// </remarks>
 	#endregion API Docs
 	public partial class View : Responder, ISupportInitializeNotification {
 
@@ -220,7 +224,6 @@ namespace Terminal.Gui {
 			TextFormatter.HotKeyChanged += TextFormatter_HotKeyChanged;
 			TextDirection = direction;
 
-			_shortcutHelper = new ShortcutHelper ();
 			CanFocus = false;
 			TabIndex = -1;
 			TabStop = false;
@@ -231,6 +234,8 @@ namespace Terminal.Gui {
 			Frame = rect.IsEmpty ? TextFormatter.CalcRect (0, 0, text, direction) : rect;
 			OnResizeNeeded ();
 
+			AddCommands ();
+
 			CreateFrames ();
 
 			LayoutFrames ();

+ 3 - 0
Terminal.Gui/View/ViewDrawing.cs

@@ -200,6 +200,9 @@ namespace Terminal.Gui {
 		/// <param name="regionScreen">The screen-relative rectangle to clear.</param>
 		public void Clear (Rect regionScreen)
 		{
+			if (Driver == null) {
+				return;
+			}
 			var prev = Driver.SetAttribute (GetNormalColor ());
 			Driver.FillRect (regionScreen);
 			Driver.SetAttribute (prev);

+ 1 - 1
Terminal.Gui/View/ViewEventArgs.cs

@@ -63,7 +63,7 @@ namespace Terminal.Gui {
 	}
 
 	/// <summary>
-	/// Defines the event arguments for <see cref="View.SetFocus(View)"/>
+	/// Defines the event arguments for <see cref="View.SetFocus()"/>
 	/// </summary>
 	public class FocusEventArgs : EventArgs {
 		/// <summary>

+ 610 - 389
Terminal.Gui/View/ViewKeyboard.cs

@@ -1,464 +1,685 @@
 using System;
 using System.Collections.Generic;
-using System.ComponentModel;
 using System.Linq;
 using System.Text;
 
-namespace Terminal.Gui {
-	public partial class View  {
-		ShortcutHelper _shortcutHelper;
-
-		/// <summary>
-		/// Event invoked when the <see cref="HotKey"/> is changed.
-		/// </summary>
-		public event EventHandler<KeyChangedEventArgs> HotKeyChanged;
-
-		Key _hotKey = Key.Null;
-
-		/// <summary>
-		/// Gets or sets the HotKey defined for this view. A user pressing HotKey on the keyboard while this view has focus will cause the Clicked event to fire.
-		/// </summary>
-		public virtual Key HotKey {
-			get => _hotKey;
-			set {
-				if (_hotKey != value) {
-					var v = value == Key.Unknown ? Key.Null : value;
-					if (_hotKey != Key.Null && ContainsKeyBinding (Key.Space | _hotKey)) {
-						if (v == Key.Null) {
-							ClearKeyBinding (Key.Space | _hotKey);
-						} else {
-							ReplaceKeyBinding (Key.Space | _hotKey, Key.Space | v);
-						}
-					} else if (v != Key.Null) {
-						AddKeyBinding (Key.Space | v, Command.Accept);
-					}
-					_hotKey = TextFormatter.HotKey = v;
-				}
+namespace Terminal.Gui;
+public partial class View {
+
+	void AddCommands ()
+	{
+		// By default, the Default command is bound to the HotKey enabling focus
+		AddCommand (Command.Default, () => {
+			if (CanFocus) {
+				SetFocus ();
+				return true;
 			}
-		}
+			return false;
+		});
 
-		/// <summary>
-		/// Gets or sets the specifier character for the hotkey (e.g. '_'). Set to '\xffff' to disable hotkey support for this View instance. The default is '\xffff'. 
-		/// </summary>
-		public virtual Rune HotKeySpecifier {
-			get {
-				if (TextFormatter != null) {
-					return TextFormatter.HotKeySpecifier;
-				} else {
-					return new Rune ('\xFFFF');
-				}
+		// By default the Accept command does nothing
+		AddCommand (Command.Accept, () => false);
+	}
+
+	#region HotKey Support
+	/// <summary>
+	/// Invoked when the <see cref="HotKey"/> is changed.
+	/// </summary>
+	public event EventHandler<KeyChangedEventArgs> HotKeyChanged;
+
+	Key _hotKey = new Key ();
+
+	void TextFormatter_HotKeyChanged (object sender, KeyChangedEventArgs e)
+	{
+		HotKeyChanged?.Invoke (this, e);
+	}
+
+	/// <summary>
+	/// Gets or sets the hot key defined for this view. Pressing the hot key on the keyboard while this view has
+	/// focus will invoke the <see cref="Command.Default"/> and <see cref="Command.Accept"/> commands. <see cref="Command.Default"/>
+	/// causes the view to be focused and <see cref="Command.Accept"/> does nothing.
+	/// By default, the HotKey is automatically set to the first
+	/// character of <see cref="Text"/> that is prefixed with with <see cref="HotKeySpecifier"/>.
+	/// <para>
+	/// A HotKey is a keypress that selects a visible UI item. For selecting items across <see cref="View"/>`s
+	/// (e.g.a <see cref="Button"/> in a <see cref="Dialog"/>) the keypress must include the <see cref="Key.WithAlt"/> modifier.
+	/// For selecting items within a View that are not Views themselves, the keypress can be key without the Alt modifier.
+	/// For example, in a Dialog, a Button with the text of "_Text" can be selected with Alt-T.
+	/// Or, in a <see cref="Menu"/> with "_File _Edit", Alt-F will select (show) the "_File" menu.
+	/// If the "_File" menu has a sub-menu of "_New" `Alt-N` or `N` will ONLY select the "_New" sub-menu if the "_File" menu is already opened.
+	/// </para>
+	/// </summary>
+	/// <remarks>
+	/// <para>
+	/// See <see href="../docs/keyboard.md"/> for an overview of Terminal.Gui keyboard APIs.
+	/// </para>
+	/// <para>
+	/// This is a helper API for configuring a key binding for the hot key. By default, this property is set whenever <see cref="Text"/> changes.
+	/// </para>
+	/// <para>
+	/// By default, when the Hot Key is set, key bindings are added for both the base key (e.g. <see cref="KeyCode.D3"/>) and
+	/// the Alt-shifted key (e.g. <see cref="KeyCode.D3"/> | <see cref="KeyCode.AltMask"/>).
+	/// This behavior can be overriden by overriding <see cref="AddKeyBindingsForHotKey"/>.
+	/// </para>
+	/// <para>
+	/// By default, when the HotKey is set to <see cref="Key.A"/> through <see cref="KeyCode.Z"/> key bindings will be added for both the un-shifted and shifted
+	/// versions. This means if the HotKey is <see cref="Key.A"/>, key bindings for <c>Key.A</c> and <c>Key.A.WithShift</c>
+	/// will be added. This behavior can be overriden by overriding <see cref="AddKeyBindingsForHotKey"/>.
+	/// </para>
+	/// <para>
+	/// If the hot key is changed, the <see cref="HotKeyChanged"/> event is fired.
+	/// </para>
+	/// <para>
+	/// Set to <see cref="KeyCode.Null"/> to disable the hot key.
+	/// </para>
+	/// </remarks>
+	public virtual Key HotKey {
+		get => _hotKey;
+		set {
+			if (value is null || value.KeyCode == KeyCode.Unknown) {
+				throw new ArgumentException (@"HotKey must not be null. Use Key.Empty to clear the HotKey.", nameof (value));
 			}
-			set {
-				TextFormatter.HotKeySpecifier = value;
-				SetHotKey ();
+			if (AddKeyBindingsForHotKey (_hotKey, value)) {
+				// This will cause TextFormatter_HotKeyChanged to be called, firing HotKeyChanged
+				_hotKey = TextFormatter.HotKey = value;
 			}
 		}
+	}
 
-		/// <summary>
-		/// This is the global setting that can be used as a global shortcut to invoke an action if provided.
-		/// </summary>
-		public Key Shortcut {
-			get => _shortcutHelper.Shortcut;
-			set {
-				if (_shortcutHelper.Shortcut != value && (ShortcutHelper.PostShortcutValidation (value) || value == Key.Null)) {
-					_shortcutHelper.Shortcut = value;
-				}
-			}
+	/// <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"/>.
+	/// </summary>
+	/// <remarks>
+	/// <para>
+	/// By default key bindings are added for both the base key (e.g. <see cref="Key.D3"/>) and
+	/// the Alt-shifted key (e.g. <c>Key.D3.WithAlt</c>
+	/// This behavior can be overriden by overriding <see cref="AddKeyBindingsForHotKey"/>.
+	/// </para>
+	/// <para>
+	/// By default, when <paramref name="hotKey"/> is <see cref="Key.A"/> through <see cref="Key.Z"/> key bindings will be added for both the un-shifted and shifted
+	/// versions. This means if the HotKey is <see cref="Key.A"/>, key bindings for <c>Key.A</c> and <c>Key.A.WithShift</c>
+	/// will be added. This behavior can be overriden by overriding <see cref="AddKeyBindingsForHotKey"/>.
+	/// </para>
+	/// <para>
+	/// For each of the bound keys <see cref="Command.Default"/> causes the view to be focused and <see cref="Command.Accept"/> does nothing.
+	/// </para>
+	/// </remarks>
+	/// <param name="prevHotKey">The HotKey <paramref name="hotKey"/> is replacing. Key bindings for this key will be removed.</param>
+	/// <param name="hotKey">The new HotKey. If <see cref="Key.Empty"/> <paramref name="prevHotKey"/> bindings will be removed.</param>
+	/// <returns><see langword="true"/> if the HotKey bindings were added.</returns>
+	/// <exception cref="ArgumentException"></exception>
+	public virtual bool AddKeyBindingsForHotKey (Key prevHotKey, Key hotKey)
+	{
+		if ((KeyCode)_hotKey == hotKey) {
+			return false;
 		}
 
-		/// <summary>
-		/// The keystroke combination used in the <see cref="Shortcut"/> as string.
-		/// </summary>
-		public string ShortcutTag => ShortcutHelper.GetShortcutTag (_shortcutHelper.Shortcut);
-
-		/// <summary>
-		/// The action to run if the <see cref="Shortcut"/> is defined.
-		/// </summary>
-		public virtual Action ShortcutAction { get; set; }
-
-		// This is null, and allocated on demand.
-		List<View> _tabIndexes;
-
-		/// <summary>
-		/// Configurable keybindings supported by the control
-		/// </summary>
-		private Dictionary<Key, Command []> KeyBindings { get; set; } = new Dictionary<Key, Command []> ();
-		private Dictionary<Command, Func<bool?>> CommandImplementations { get; set; } = new Dictionary<Command, Func<bool?>> ();
-
-		/// <summary>
-		/// This returns a tab index list of the subviews contained by this view.
-		/// </summary>
-		/// <value>The tabIndexes.</value>
-		public IList<View> TabIndexes => _tabIndexes?.AsReadOnly () ?? _empty;
-
-		int _tabIndex = -1;
-
-		/// <summary>
-		/// Indicates the index of the current <see cref="View"/> from the <see cref="TabIndexes"/> list.
-		/// </summary>
-		public int TabIndex {
-			get { return _tabIndex; }
-			set {
-				if (!CanFocus) {
-					_tabIndex = -1;
-					return;
-				} else if (SuperView?._tabIndexes == null || SuperView?._tabIndexes.Count == 1) {
-					_tabIndex = 0;
-					return;
-				} else if (_tabIndex == value) {
-					return;
-				}
-				_tabIndex = value > SuperView._tabIndexes.Count - 1 ? SuperView._tabIndexes.Count - 1 : value < 0 ? 0 : value;
-				_tabIndex = GetTabIndex (_tabIndex);
-				if (SuperView._tabIndexes.IndexOf (this) != _tabIndex) {
-					SuperView._tabIndexes.Remove (this);
-					SuperView._tabIndexes.Insert (_tabIndex, this);
-					SetTabIndex ();
-				}
-			}
-		}
+		var newKey = hotKey == KeyCode.Unknown ? KeyCode.Null : hotKey;
 
-		int GetTabIndex (int idx)
-		{
-			var i = 0;
-			foreach (var v in SuperView._tabIndexes) {
-				if (v._tabIndex == -1 || v == this) {
-					continue;
-				}
-				i++;
-			}
-			return Math.Min (i, idx);
+		var baseKey = newKey.NoAlt.NoShift.NoCtrl;
+		if (newKey != Key.Empty && (baseKey == Key.Space || Rune.IsControl (baseKey.AsRune))) {
+			throw new ArgumentException (@$"HotKey must be a printable (and non-space) key ({hotKey}).");
 		}
 
-		void SetTabIndex ()
-		{
-			var i = 0;
-			foreach (var v in SuperView._tabIndexes) {
-				if (v._tabIndex == -1) {
-					continue;
-				}
-				v._tabIndex = i;
-				i++;
+		if (newKey != baseKey) {
+			if (newKey.IsCtrl) {
+				throw new ArgumentException (@$"HotKey does not support CtrlMask ({hotKey}).");
 			}
+			// Strip off the shift mask if it's A...Z
+			if (baseKey.IsKeyCodeAtoZ) {
+				newKey = newKey.NoShift;
+			}
+			// Strip off the Alt mask
+			newKey = newKey.NoAlt;
 		}
 
-		bool _tabStop = true;
-
-		/// <summary>
-		/// This only be <see langword="true"/> if the <see cref="CanFocus"/> is also <see langword="true"/> 
-		/// and the focus can be avoided by setting this to <see langword="false"/>
-		/// </summary>
-		public bool TabStop {
-			get => _tabStop;
-			set {
-				if (_tabStop == value) {
-					return;
-				}
-				_tabStop = CanFocus && value;
-			}
+		// Remove base version
+		if (KeyBindings.TryGet (prevHotKey, out _)) {
+			KeyBindings.Remove (prevHotKey);
 		}
 
-		int _oldTabIndex;
-		
-		/// <summary>
-		/// Invoked when a character key is pressed and occurs after the key up event.
-		/// </summary>
-		public event EventHandler<KeyEventEventArgs> KeyPressed;
+		// Remove the Alt version
+		if (KeyBindings.TryGet (prevHotKey.WithAlt, out _)) {
+			KeyBindings.Remove (prevHotKey.WithAlt);
+		}
 
-		/// <inheritdoc/>
-		public override bool ProcessKey (KeyEvent keyEvent)
-		{
-			if (!Enabled) {
-				return false;
+		if (_hotKey.KeyCode is >= KeyCode.A and <= KeyCode.Z) {
+			// Remove the shift version
+			if (KeyBindings.TryGet (prevHotKey.WithShift, out _)) {
+				KeyBindings.Remove (prevHotKey.WithShift);
 			}
-
-			var args = new KeyEventEventArgs (keyEvent);
-			KeyPressed?.Invoke (this, args);
-			if (args.Handled)
-				return true;
-			if (Focused?.Enabled == true) {
-				Focused?.KeyPressed?.Invoke (this, args);
-				if (args.Handled)
-					return true;
+			// Remove alt | shift version
+			if (KeyBindings.TryGet (prevHotKey.WithShift.WithAlt, out _)) {
+				KeyBindings.Remove (prevHotKey.WithShift.WithAlt);
 			}
-
-			return Focused?.Enabled == true && Focused?.ProcessKey (keyEvent) == true;
 		}
 
-		/// <summary>
-		/// Invokes any binding that is registered on this <see cref="View"/>
-		/// and matches the <paramref name="keyEvent"/>
-		/// </summary>
-		/// <param name="keyEvent">The key event passed.</param>
-		protected bool? InvokeKeybindings (KeyEvent keyEvent)
-		{
-			bool? toReturn = null;
-
-			if (KeyBindings.ContainsKey (keyEvent.Key)) {
-
-				foreach (var command in KeyBindings [keyEvent.Key]) {
+		// Add the new 
+		if (newKey != KeyCode.Null) {
+			// Add the base and Alt key
+			KeyBindings.Add (newKey, KeyBindingScope.HotKey, Command.Default, Command.Accept);
+			KeyBindings.Add (newKey.WithAlt, KeyBindingScope.HotKey, Command.Default, Command.Accept);
 
-					if (!CommandImplementations.ContainsKey (command)) {
-						throw new NotSupportedException ($"A KeyBinding was set up for the command {command} ({keyEvent.Key}) but that command is not supported by this View ({GetType ().Name})");
-					}
-
-					// each command has its own return value
-					var thisReturn = CommandImplementations [command] ();
+			// If the Key is A..Z, add ShiftMask and AltMask | ShiftMask
+			if (newKey.IsKeyCodeAtoZ) {
+				KeyBindings.Add (newKey.WithShift, KeyBindingScope.HotKey, Command.Default, Command.Accept);
+				KeyBindings.Add (newKey.WithShift.WithAlt, KeyBindingScope.HotKey, Command.Default, Command.Accept);
+			}
+		}
+		return true;
+	}
 
-					// if we haven't got anything yet, the current command result should be used
-					if (toReturn == null) {
-						toReturn = thisReturn;
-					}
 
-					// if ever see a true then that's what we will return
-					if (thisReturn ?? false) {
-						toReturn = true;
-					}
-				}
+	/// <summary>
+	/// Gets or sets the specifier character for the hot key (e.g. '_'). Set to '\xffff' to disable automatic hot key setting
+	/// support for this View instance. The default is '\xffff'. 
+	/// </summary>
+	public virtual Rune HotKeySpecifier {
+		get {
+			if (TextFormatter != null) {
+				return TextFormatter.HotKeySpecifier;
+			} else {
+				return new Rune ('\xFFFF');
 			}
+		}
+		set {
+			TextFormatter.HotKeySpecifier = value;
+			SetHotKey ();
+		}
+	}
 
-			return toReturn;
-		}
-
-		/// <summary>
-		/// <para>Adds a new key combination that will trigger the given <paramref name="command"/>
-		/// (if supported by the View - see <see cref="GetSupportedCommands"/>)
-		/// </para>
-		/// <para>If the key is already bound to a different <see cref="Command"/> it will be
-		/// rebound to this one</para>
-		/// <remarks>Commands are only ever applied to the current <see cref="View"/>(i.e. this feature
-		/// cannot be used to switch focus to another view and perform multiple commands there) </remarks>
-		/// </summary>
-		/// <param name="key"></param>
-		/// <param name="command">The command(s) to run on the <see cref="View"/> when <paramref name="key"/> is pressed.
-		/// When specifying multiple commands, all commands will be applied in sequence. The bound <paramref name="key"/> strike
-		/// will be consumed if any took effect.</param>
-		public void AddKeyBinding (Key key, params Command [] command)
-		{
-			if (command.Length == 0) {
-				throw new ArgumentException ("At least one command must be specified", nameof (command));
+	void SetHotKey ()
+	{
+		if (TextFormatter == null || HotKeySpecifier == new Rune ('\xFFFF')) {
+			return; // throw new InvalidOperationException ("Can't set HotKey unless a TextFormatter has been created");
+		}
+		if (TextFormatter.FindHotKey (_text, HotKeySpecifier, true, out _, out var hk)) {
+			if (_hotKey.KeyCode != hk && hk != KeyCode.Unknown) {
+				HotKey = hk;
 			}
+		} else {
+			HotKey = KeyCode.Null;
+		}
+	}
 
-			if (KeyBindings.ContainsKey (key)) {
-				KeyBindings [key] = command;
-			} else {
-				KeyBindings.Add (key, command);
+	#endregion HotKey Support
+
+	#region Tab/Focus Handling
+	// This is null, and allocated on demand.
+	List<View> _tabIndexes;
+
+	/// <summary>
+	/// Gets a list of the subviews that are <see cref="TabStop"/>s.
+	/// </summary>
+	/// <value>The tabIndexes.</value>
+	public IList<View> TabIndexes => _tabIndexes?.AsReadOnly () ?? _empty;
+
+	int _tabIndex = -1;
+	int _oldTabIndex;
+
+	/// <summary>
+	/// Indicates the index of the current <see cref="View"/> from the <see cref="TabIndexes"/> list. See also: <seealso cref="TabStop"/>.
+	/// </summary>
+	public int TabIndex {
+		get { return _tabIndex; }
+		set {
+			if (!CanFocus) {
+				_tabIndex = -1;
+				return;
+			} else if (SuperView?._tabIndexes == null || SuperView?._tabIndexes.Count == 1) {
+				_tabIndex = 0;
+				return;
+			} else if (_tabIndex == value) {
+				return;
+			}
+			_tabIndex = value > SuperView._tabIndexes.Count - 1 ? SuperView._tabIndexes.Count - 1 : value < 0 ? 0 : value;
+			_tabIndex = GetTabIndex (_tabIndex);
+			if (SuperView._tabIndexes.IndexOf (this) != _tabIndex) {
+				SuperView._tabIndexes.Remove (this);
+				SuperView._tabIndexes.Insert (_tabIndex, this);
+				SetTabIndex ();
 			}
 		}
+	}
 
-		/// <summary>
-		/// Replaces a key combination already bound to <see cref="Command"/>.
-		/// </summary>
-		/// <param name="fromKey">The key to be replaced.</param>
-		/// <param name="toKey">The new key to be used.</param>
-		protected void ReplaceKeyBinding (Key fromKey, Key toKey)
-		{
-			if (KeyBindings.ContainsKey (fromKey)) {
-				var value = KeyBindings [fromKey];
-				KeyBindings.Remove (fromKey);
-				KeyBindings [toKey] = value;
+	int GetTabIndex (int idx)
+	{
+		var i = 0;
+		foreach (var v in SuperView._tabIndexes) {
+			if (v._tabIndex == -1 || v == this) {
+				continue;
 			}
+			i++;
 		}
+		return Math.Min (i, idx);
+	}
 
-		/// <summary>
-		/// Checks if the key binding already exists.
-		/// </summary>
-		/// <param name="key">The key to check.</param>
-		/// <returns><see langword="true"/> If the key already exist, <see langword="false"/> otherwise.</returns>
-		public bool ContainsKeyBinding (Key key)
-		{
-			return KeyBindings.ContainsKey (key);
-		}
-
-		/// <summary>
-		/// Removes all bound keys from the View and resets the default bindings.
-		/// </summary>
-		public void ClearKeyBindings ()
-		{
-			KeyBindings.Clear ();
-		}
-
-		/// <summary>
-		/// Clears the existing keybinding (if any) for the given <paramref name="key"/>.
-		/// </summary>
-		/// <param name="key"></param>
-		public void ClearKeyBinding (Key key)
-		{
-			KeyBindings.Remove (key);
-		}
-
-		/// <summary>
-		/// Removes all key bindings that trigger the given command. Views can have multiple different
-		/// keys bound to the same command and this method will clear all of them.
-		/// </summary>
-		/// <param name="command"></param>
-		public void ClearKeyBinding (params Command [] command)
-		{
-			foreach (var kvp in KeyBindings.Where (kvp => kvp.Value.SequenceEqual (command)).ToArray ()) {
-				KeyBindings.Remove (kvp.Key);
+	void SetTabIndex ()
+	{
+		var i = 0;
+		foreach (var v in SuperView._tabIndexes) {
+			if (v._tabIndex == -1) {
+				continue;
 			}
+			v._tabIndex = i;
+			i++;
 		}
+	}
 
-		/// <summary>
-		/// <para>States that the given <see cref="View"/> supports a given <paramref name="command"/>
-		/// and what <paramref name="f"/> to perform to make that command happen
-		/// </para>
-		/// <para>If the <paramref name="command"/> already has an implementation the <paramref name="f"/>
-		/// will replace the old one</para>
-		/// </summary>
-		/// <param name="command">The command.</param>
-		/// <param name="f">The function.</param>
-		protected void AddCommand (Command command, Func<bool?> f)
-		{
-			// if there is already an implementation of this command
-			if (CommandImplementations.ContainsKey (command)) {
-				// replace that implementation
-				CommandImplementations [command] = f;
-			} else {
-				// else record how to perform the action (this should be the normal case)
-				CommandImplementations.Add (command, f);
+	bool _tabStop = true;
+
+	/// <summary>
+	/// Gets or sets whether the view is a stop-point for keyboard navigation of focus. Will be
+	/// <see langword="true"/> only if the <see cref="CanFocus"/> is also <see langword="true"/>.
+	/// Set to <see langword="false"/> to prevent the view from being a stop-point for keyboard navigation.
+	/// </summary>
+	/// <remarks>
+	/// The default keyboard navigation keys are <c>Key.Tab</c> and <c>Key>Tab.WithShift</c>.
+	/// These can be changed by modifying the key bindings (see <see cref="KeyBindings.Add(Key, Command[])"/>) of the SuperView.
+	/// </remarks>
+	public bool TabStop {
+		get => _tabStop;
+		set {
+			if (_tabStop == value) {
+				return;
 			}
+			_tabStop = CanFocus && value;
 		}
+	}
 
-		/// <summary>
-		/// Returns all commands that are supported by this <see cref="View"/>.
-		/// </summary>
-		/// <returns></returns>
-		public IEnumerable<Command> GetSupportedCommands ()
-		{
-			return CommandImplementations.Keys;
+	#endregion Tab/Focus Handling
+
+	#region Low-level Key handling
+
+	#region Key Down Event
+	/// <summary>
+	/// If the view is enabled, processes a new key down event and returns <see langword="true"/> if the event was handled.
+	/// </summary>
+	/// <remarks>
+	/// <para>
+	/// If the view has a sub view that is focused, <see cref="NewKeyDownEvent"/> will be called on the focused view first.
+	/// </para>
+	/// <para>
+	/// If the focused sub view does not handle the key press, this method calls <see cref="OnKeyDown"/> to allow the view
+	/// to pre-process the key press. If <see cref="OnKeyDown"/> returns <see langword="false"/>, this method then calls
+	/// <see cref="OnInvokingKeyBindings"/> to invoke any key bindings. Then, only if no key bindings are handled,
+	/// <see cref="OnProcessKeyDown"/> will be called allowing the view to process the key press.
+	/// </para>
+	/// <para>
+	/// See <see href="../docs/keyboard.md">for an overview of Terminal.Gui keyboard APIs.</see>
+	/// </para>
+	/// </remarks>
+	/// <param name="keyEvent"></param>
+	/// <returns><see langword="true"/> if the event was handled.</returns>
+	public bool NewKeyDownEvent (Key keyEvent)
+	{
+		if (!Enabled) {
+			return false;
 		}
 
-		/// <summary>
-		/// Gets the key used by a command.
-		/// </summary>
-		/// <param name="command">The command to search.</param>
-		/// <returns>The <see cref="Key"/> used by a <see cref="Command"/></returns>
-		public Key GetKeyFromCommand (params Command [] command)
-		{
-			return KeyBindings.First (kb => kb.Value.SequenceEqual (command)).Key;
+		// By default the KeyBindingScope is View
+
+		if (Focused?.NewKeyDownEvent (keyEvent) == true) {
+			return true;
 		}
 
-		/// <inheritdoc/>
-		public override bool ProcessHotKey (KeyEvent keyEvent)
-		{
-			if (!Enabled) {
-				return false;
-			}
+		// Before (fire the cancellable event)
+		if (OnKeyDown (keyEvent)) {
+			return true;
+		}
 
-			var args = new KeyEventEventArgs (keyEvent);
-			if (MostFocused?.Enabled == true) {
-				MostFocused?.KeyPressed?.Invoke (this, args);
-				if (args.Handled)
-					return true;
-			}
-			if (MostFocused?.Enabled == true && MostFocused?.ProcessKey (keyEvent) == true)
-				return true;
-			if (_subviews == null || _subviews.Count == 0)
-				return false;
+		// During (this is what can be cancelled)
+		var handled = OnInvokingKeyBindings (keyEvent);
+		if (handled != null && (bool)handled) {
+			return true;
+		}
 
-			foreach (var view in _subviews)
-				if (view.Enabled && view.ProcessHotKey (keyEvent))
-					return true;
-			return false;
+		// TODO: The below is not right. OnXXX handlers are supposed to fire the events.
+		// TODO: But I've moved it outside of the v-function to test something.
+		// After (fire the cancellable event)
+		// fire event
+		ProcessKeyDown?.Invoke (this, keyEvent);
+		if (!keyEvent.Handled && OnProcessKeyDown (keyEvent)) {
+			return true;
 		}
 
-		/// <inheritdoc/>
-		public override bool ProcessColdKey (KeyEvent keyEvent)
-		{
-			if (!Enabled) {
-				return false;
-			}
 
-			var args = new KeyEventEventArgs (keyEvent);
-			KeyPressed?.Invoke (this, args);
-			if (args.Handled)
-				return true;
-			if (MostFocused?.Enabled == true) {
-				MostFocused?.KeyPressed?.Invoke (this, args);
-				if (args.Handled)
-					return true;
-			}
-			if (MostFocused?.Enabled == true && MostFocused?.ProcessKey (keyEvent) == true)
-				return true;
-			if (_subviews == null || _subviews.Count == 0)
-				return false;
+		return keyEvent.Handled;
+	}
 
-			foreach (var view in _subviews)
-				if (view.Enabled && view.ProcessColdKey (keyEvent))
-					return true;
+	/// <summary>
+	/// Low-level API called when the user presses a key, allowing a view to pre-process the key down event.
+	/// This is called from <see cref="NewKeyDownEvent"/> before <see cref="OnInvokingKeyBindings"/>.
+	/// </summary>
+	/// <param name="keyEvent">Contains the details about the key that produced the event.</param>
+	/// <returns><see langword="false"/> if the key press was not handled. <see langword="true"/> if
+	/// the keypress was handled and no other view should see it.</returns>
+	/// <remarks>
+	/// <para>
+	/// For processing <see cref="HotKey"/>s and commands, use <see cref="Command"/> and <see cref="KeyBindings.Add(Key, Command[])"/>instead.
+	/// </para>
+	/// <para>
+	/// Fires the <see cref="KeyDown"/> event. 
+	/// </para>
+	/// </remarks>
+	public virtual bool OnKeyDown (Key keyEvent)
+	{
+		// fire event
+		KeyDown?.Invoke (this, keyEvent);
+		return keyEvent.Handled;
+	}
+
+	/// <summary>
+	/// Invoked when the user presses a key, allowing subscribers to pre-process the key down event.
+	/// This is fired from <see cref="OnKeyDown"/> before <see cref="OnInvokingKeyBindings"/>.
+	/// Set <see cref="Key.Handled"/> to true to stop the key from
+	/// being processed by other views. 
+	/// </summary>
+	/// <remarks>
+	/// <para>
+	/// Not all terminals support key distinct up notifications, Applications should avoid
+	/// depending on distinct KeyUp events.
+	/// </para>
+	/// <para>
+	/// See <see href="../docs/keyboard.md">for an overview of Terminal.Gui keyboard APIs.</see>
+	/// </para>
+	/// </remarks>
+	public event EventHandler<Key> KeyDown;
+
+	/// <summary>
+	/// Low-level API called when the user presses a key, allowing views do things during key down events.
+	/// This is called from <see cref="NewKeyDownEvent"/> after <see cref="OnInvokingKeyBindings"/>. 
+	/// </summary>
+	/// <param name="keyEvent">Contains the details about the key that produced the event.</param>
+	/// <returns><see langword="false"/> if the key press was not handled. <see langword="true"/> if
+	/// the keypress was handled and no other view should see it.</returns>
+	/// <remarks>
+	/// <para>
+	/// Override <see cref="OnProcessKeyDown"/> to override the behavior of how the base class processes key down events.
+	/// </para>
+	/// <para>
+	/// For processing <see cref="HotKey"/>s and commands, use <see cref="Command"/> and <see cref="KeyBindings.Add(Key, Command[])"/>instead.
+	/// </para>
+	/// <para>
+	/// Fires the <see cref="ProcessKeyDown"/> event. 
+	/// </para>
+	/// <para>
+	/// Not all terminals support distinct key up notifications; applications should avoid
+	/// depending on distinct KeyUp events.
+	/// </para>
+	/// </remarks>
+	public virtual bool OnProcessKeyDown (Key keyEvent)
+	{
+		//ProcessKeyDown?.Invoke (this, keyEvent);
+		return keyEvent.Handled;
+	}
+
+	/// <summary>
+	/// Invoked when the users presses a key, allowing subscribers to do things during key down events.
+	/// Set <see cref="Key.Handled"/> to true to stop the key from
+	/// being processed by other views. Invoked after <see cref="KeyDown"/> and before <see cref="InvokingKeyBindings"/>.
+	/// </summary>
+	/// <remarks>
+	/// <para>
+	/// SubViews can use the <see cref="ProcessKeyDown"/> of their super view override the default behavior of
+	/// when key bindings are invoked.
+	/// </para>
+	/// <para>
+	/// Not all terminals support distinct key up notifications; applications should avoid
+	/// depending on distinct KeyUp events.
+	/// </para>
+	/// <para>
+	/// See <see href="../docs/keyboard.md">for an overview of Terminal.Gui keyboard APIs.</see>
+	/// </para>
+	/// </remarks>
+	public event EventHandler<Key> ProcessKeyDown;
+
+	#endregion KeyDown Event
+
+	#region KeyUp Event
+	/// <summary>
+	/// If the view is enabled, processes a new key up event and returns <see langword="true"/> if the event was handled.
+	/// Called before <see cref="NewKeyDownEvent"/>.
+	/// </summary>
+	/// <remarks>
+	/// <para>
+	/// Not all terminals support key distinct down/up notifications, Applications should avoid
+	/// depending on distinct KeyUp events.
+	/// </para>
+	/// <para>
+	/// If the view has a sub view that is focused, <see cref="NewKeyUpEvent"/> will be called on the focused view first.
+	/// </para>
+	/// <para>
+	/// If the focused sub view does not handle the key press, this method calls <see cref="OnKeyUp"/>, which is cancellable.
+	/// </para>
+	/// <para>
+	/// See <see href="../docs/keyboard.md">for an overview of Terminal.Gui keyboard APIs.</see>
+	/// </para>
+	/// </remarks>
+	/// <param name="keyEvent"></param>
+	/// <returns><see langword="true"/> if the event was handled.</returns>
+	public bool NewKeyUpEvent (Key keyEvent)
+	{
+		if (!Enabled) {
 			return false;
 		}
 
-		/// <summary>
-		/// Invoked when a key is pressed.
-		/// </summary>
-		public event EventHandler<KeyEventEventArgs> KeyDown;
+		if (Focused?.NewKeyUpEvent (keyEvent) == true) {
+			return true;
+		}
 
-		/// <inheritdoc/>
-		public override bool OnKeyDown (KeyEvent keyEvent)
-		{
-			if (!Enabled) {
-				return false;
-			}
+		// Before (fire the cancellable event)
+		if (OnKeyUp (keyEvent)) {
+			return true;
+		}
 
-			var args = new KeyEventEventArgs (keyEvent);
-			KeyDown?.Invoke (this, args);
-			if (args.Handled) {
-				return true;
-			}
-			if (Focused?.Enabled == true) {
-				Focused.KeyDown?.Invoke (this, args);
-				if (args.Handled) {
-					return true;
-				}
-				if (Focused?.OnKeyDown (keyEvent) == true) {
-					return true;
-				}
-			}
+		// During (this is what can be cancelled)
+		// TODO: Until there's a clear use-case, we will not define 'during' event (e.g. OnDuringKeyUp). 
 
-			return false;
+		// After (fire the cancellable event InvokingKeyBindings)
+		// TODO: Until there's a clear use-case, we will not define an 'after' event (e.g. OnAfterKeyUp). 
+
+		return false;
+	}
+
+	/// <summary>
+	/// Method invoked when a key is released. This method is called from <see cref="NewKeyUpEvent"/>.
+	/// </summary>
+	/// <param name="keyEvent">Contains the details about the key that produced the event.</param>
+	/// <returns><see langword="false"/> if the key stroke was not handled. <see langword="true"/> if no
+	/// other view should see it.</returns>
+	/// <remarks>
+	/// Not all terminals support key distinct down/up notifications, Applications should avoid
+	/// depending on distinct KeyUp events.
+	/// <para>
+	/// Overrides must call into the base and return <see langword="true"/> if the base returns <see langword="true"/>.
+	/// </para>
+	/// <para>
+	/// See <see href="../docs/keyboard.md">for an overview of Terminal.Gui keyboard APIs.</see>
+	/// </para>
+	/// </remarks>
+	public virtual bool OnKeyUp (Key keyEvent)
+	{
+		// fire event
+		KeyUp?.Invoke (this, keyEvent);
+		if (keyEvent.Handled) {
+			return true;
 		}
 
-		/// <summary>
-		/// Invoked when a key is released.
-		/// </summary>
-		public event EventHandler<KeyEventEventArgs> KeyUp;
+		return false;
+	}
 
-		/// <inheritdoc/>
-		public override bool OnKeyUp (KeyEvent keyEvent)
-		{
-			if (!Enabled) {
-				return false;
-			}
+	/// <summary>
+	/// Invoked when a key is released. Set <see cref="Key.Handled"/> to true to stop the key up event from being processed by other views.
+	/// <remarks>
+	/// Not all terminals support key distinct down/up notifications, Applications should avoid
+	/// depending on distinct KeyDown and KeyUp events and instead should use <see cref="KeyDown"/>.
+	/// <para>
+	/// See <see href="../docs/keyboard.md">for an overview of Terminal.Gui keyboard APIs.</see>
+	/// </para>
+	/// </remarks>
+	/// </summary>
+	public event EventHandler<Key> KeyUp;
+
+	#endregion KeyUp Event
+
+	#endregion Low-level Key handling
+
+	#region Key Bindings
+
+	/// <summary>
+	/// Gets the key bindings for this view.
+	/// </summary>
+	public KeyBindings KeyBindings { get; } = new ();
+	private Dictionary<Command, Func<bool?>> CommandImplementations { get; } = new ();
+
+	/// <summary>
+	/// Low-level API called when a user presses a key; invokes any key bindings set on the view.
+	/// This is called during <see cref="NewKeyDownEvent"/> after <see cref="OnKeyDown"/> has returned.
+	/// </summary>
+	/// <remarks>
+	/// <para>
+	/// Fires the <see cref="InvokingKeyBindings"/> event.
+	/// </para>
+	/// <para>
+	/// See <see href="../docs/keyboard.md">for an overview of Terminal.Gui keyboard APIs.</see>
+	/// </para>
+	/// </remarks>
+	/// <param name="keyEvent">Contains the details about the key that produced the event.</param>
+	/// <returns><see langword="false"/> if the key press was not handled. <see langword="true"/> if
+	/// the keypress was handled and no other view should see it.</returns>
+	public virtual bool? OnInvokingKeyBindings (Key keyEvent)
+	{
+		// fire event
+		// BUGBUG: KeyEventArgs doesn't include scope, so the event never sees it.
+		InvokingKeyBindings?.Invoke (this, keyEvent);
+		if (keyEvent.Handled) {
+			return true;
+		}
 
-			var args = new KeyEventEventArgs (keyEvent);
-			KeyUp?.Invoke (this, args);
-			if (args.Handled) {
-				return true;
-			}
-			if (Focused?.Enabled == true) {
-				Focused.KeyUp?.Invoke (this, args);
-				if (args.Handled) {
-					return true;
-				}
-				if (Focused?.OnKeyUp (keyEvent) == true) {
+		// * If no key binding was found, `InvokeKeyBindings` returns `null`.
+		//   Continue passing the event (return `false` from `OnInvokeKeyBindings`).
+		// * If key bindings were found, but none handled the key (all `Command`s returned `false`),
+		//   `InvokeKeyBindings` returns `false`. Continue passing the event (return `false` from `OnInvokeKeyBindings`)..
+		// * If key bindings were found, and any handled the key (at least one `Command` returned `true`),
+		//   `InvokeKeyBindings` returns `true`. Continue passing the event (return `false` from `OnInvokeKeyBindings`).
+		var handled = InvokeKeyBindings (keyEvent);
+		if (handled != null && (bool)handled) {
+			// Stop processing if any key binding handled the key.
+			// DO NOT stop processing if there are no matching key bindings or none of the key bindings handled the key
+			return true;
+		}
+
+		// Now, process any key bindings in the subviews that are tagged to KeyBindingScope.HotKey.
+		foreach (var view in Subviews.Where (v => v.KeyBindings.TryGet (keyEvent.KeyCode, KeyBindingScope.HotKey, out var _))) {
+			// TODO: I think this TryGet is not needed due to the one in the lambda above. Use `Get` instead?
+			if (view.KeyBindings.TryGet (keyEvent.KeyCode, KeyBindingScope.HotKey, out var binding)) {
+				keyEvent.Scope = KeyBindingScope.HotKey;
+				handled = view.OnInvokingKeyBindings (keyEvent);
+				if (handled != null && (bool)handled) {
 					return true;
 				}
 			}
+		}
 
-			return false;
+		return handled;
+	}
+
+	/// <summary>
+	/// Invoked when a key is pressed that may be mapped to a key binding. Set <see cref="Key.Handled"/>
+	/// to true to stop the key from being processed by other views. 
+	/// </summary>
+	public event EventHandler<Key> InvokingKeyBindings;
+
+	/// <summary>
+	/// Invokes any binding that is registered on this <see cref="View"/>
+	/// and matches the <paramref name="keyEvent"/>
+	/// <para>
+	/// See <see href="../docs/keyboard.md">for an overview of Terminal.Gui keyboard APIs.</see>
+	/// </para>
+	/// </summary>
+	/// <param name="keyEvent">The key event passed.</param>
+	/// <returns>
+	/// <see langword="null"/> if no command was bound the <paramref name="keyEvent"/>.
+	/// <see langword="true"/> if commands were invoked and at least one handled the command.
+	/// <see langword="false"/> if commands were invoked and at none handled the command.
+	/// </returns>	
+	protected bool? InvokeKeyBindings (Key keyEvent)
+	{
+		bool? toReturn = null;
+		var key = keyEvent.KeyCode;
+		if (!KeyBindings.TryGet (key, out var binding)) {
+			return null;
 		}
-		
-		void SetHotKey ()
-		{
-			if (TextFormatter == null) {
-				return; // throw new InvalidOperationException ("Can't set HotKey unless a TextFormatter has been created");
+		foreach (var command in binding.Commands) {
+
+			if (!CommandImplementations.ContainsKey (command)) {
+				throw new NotSupportedException (@$"A KeyBinding was set up for the command {command} ({keyEvent.KeyCode}) but that command is not supported by this View ({GetType ().Name})");
 			}
-			TextFormatter.FindHotKey (_text, HotKeySpecifier, true, out _, out var hk);
-			if (_hotKey != hk) {
-				HotKey = hk;
+
+			// each command has its own return value
+			var thisReturn = InvokeCommand (command);
+
+			// if we haven't got anything yet, the current command result should be used
+			toReturn ??= thisReturn;
+
+			// if ever see a true then that's what we will return
+			if (thisReturn ?? false) {
+				toReturn = true;
 			}
 		}
+
+		return toReturn;
+	}
+
+	/// <summary>
+	/// Invokes the specified command.
+	/// </summary>
+	/// <param name="command"></param>
+	/// <returns>
+	/// <see langword="null"/> if no command was found.
+	/// <see langword="true"/> if the command was invoked and it handled the command.
+	/// <see langword="false"/> if the command was invoked and it did not handle the command.
+	/// </returns>		
+	public bool? InvokeCommand (Command command)
+	{
+		if (!CommandImplementations.ContainsKey (command)) {
+			return null;
+		}
+		return CommandImplementations [command] ();
 	}
+
+	/// <summary>
+	/// <para>
+	/// Sets the function that will be invoked for a <see cref="Command"/>. Views should call <see cref="AddCommand"/>
+	/// for each command they support. 
+	/// </para>
+	/// <para>
+	/// If <see cref="AddCommand"/> has already been called for <paramref name="command"/> <paramref name="f"/> will replace the old one.</para>
+	/// </summary>
+	/// <param name="command">The command.</param>
+	/// <param name="f">The function.</param>
+	protected void AddCommand (Command command, Func<bool?> f)
+	{
+		// if there is already an implementation of this command
+		// replace that implementation
+		// else record how to perform the action (this should be the normal case)
+		if (CommandImplementations != null) {
+			CommandImplementations [command] = f;
+		}
+	}
+
+	/// <summary>
+	/// Returns all commands that are supported by this <see cref="View"/>.
+	/// </summary>
+	/// <returns></returns>
+	public IEnumerable<Command> GetSupportedCommands ()
+	{
+		return CommandImplementations.Keys;
+	}
+
+	// TODO: Add GetKeysBoundToCommand() - given a Command, return all Keys that would invoke it
+
+	#endregion Key Bindings
 }

+ 0 - 5
Terminal.Gui/View/ViewText.cs

@@ -48,11 +48,6 @@ namespace Terminal.Gui {
 		/// </summary>
 		public TextFormatter TextFormatter { get; set; }
 
-		void TextFormatter_HotKeyChanged (object sender, KeyChangedEventArgs e)
-		{
-			HotKeyChanged?.Invoke (this, e);
-		}
-
 		/// <summary>
 		/// Can be overridden if the <see cref="Terminal.Gui.TextFormatter.Text"/> has
 		///  different format than the default.

+ 195 - 270
Terminal.Gui/Views/Button.cs

@@ -8,304 +8,229 @@
 using System;
 using System.Text;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui;
+/// <summary>
+///   Button is a <see cref="View"/> that provides an item that invokes raises the <see cref="Clicked"/> event.
+/// </summary>
+/// <remarks>
+/// <para>
+///   Provides a button showing text that raises the <see cref="Clicked"/> event when clicked on with a mouse
+///   or when the user presses SPACE, ENTER, or the <see cref="View.HotKey"/>. The hot key is the first letter or digit following the first underscore ('_') 
+///   in the button text. 
+/// </para>
+/// <para>
+///   Use <see cref="View.HotKeySpecifier"/> to change the hot key specifier from the default of ('_'). 
+/// </para>
+/// <para>
+///   If no hot key specifier is found, the first uppercase letter encountered will be used as the hot key.
+/// </para>
+/// <para>
+///   When the button is configured as the default (<see cref="IsDefault"/>) and the user presses
+///   the ENTER key, if no other <see cref="View"/> processes the key, the <see cref="Button"/>'s
+///   <see cref="Clicked"/> event will will be fired.
+/// </para>
+/// </remarks>
+public class Button : View {
+	bool _isDefault;
+	Rune _leftBracket;
+	Rune _rightBracket;
+	Rune _leftDefault;
+	Rune _rightDefault;
+
 	/// <summary>
-	///   Button is a <see cref="View"/> that provides an item that invokes raises the <see cref="Clicked"/> event.
+	///   Initializes a new instance of <see cref="Button"/> using <see cref="LayoutStyle.Computed"/> layout.
 	/// </summary>
 	/// <remarks>
-	/// <para>
-	///   Provides a button showing text that raises the <see cref="Clicked"/> event when clicked on with a mouse
-	///   or when the user presses SPACE, ENTER, or hotkey. The hotkey is the first letter or digit following the first underscore ('_') 
-	///   in the button text. 
-	/// </para>
-	/// <para>
-	///   Use <see cref="View.HotKeySpecifier"/> to change the hotkey specifier from the default of ('_'). 
-	/// </para>
-	/// <para>
-	///   If no hotkey specifier is found, the first uppercase letter encountered will be used as the hotkey.
-	/// </para>
-	/// <para>
-	///   When the button is configured as the default (<see cref="IsDefault"/>) and the user presses
-	///   the ENTER key, if no other <see cref="View"/> processes the <see cref="KeyEvent"/>, the <see cref="Button"/>'s
-	///   <see cref="Clicked"/> event will will be fired.
-	/// </para>
+	///   The width of the <see cref="Button"/> is computed based on the
+	///   text length. The height will always be 1.
 	/// </remarks>
-	public class Button : View {
-		bool is_default;
-		Rune _leftBracket;
-		Rune _rightBracket;
-		Rune _leftDefault;
-		Rune _rightDefault;
-
-		/// <summary>
-		///   Initializes a new instance of <see cref="Button"/> using <see cref="LayoutStyle.Computed"/> layout.
-		/// </summary>
-		/// <remarks>
-		///   The width of the <see cref="Button"/> is computed based on the
-		///   text length. The height will always be 1.
-		/// </remarks>
-		public Button () : this (text: string.Empty, is_default: false) { }
-
-		/// <summary>
-		///   Initializes a new instance of <see cref="Button"/> using <see cref="LayoutStyle.Computed"/> layout.
-		/// </summary>
-		/// <remarks>
-		///   The width of the <see cref="Button"/> is computed based on the
-		///   text length. The height will always be 1.
-		/// </remarks>
-		/// <param name="text">The button's text</param>
-		/// <param name="is_default">
-		///   If <c>true</c>, a special decoration is used, and the user pressing the enter key 
-		///   in a <see cref="Dialog"/> will implicitly activate this button.
-		/// </param>
-		public Button (string text, bool is_default = false) : base (text)
-		{
-			SetInitialProperties (text, is_default);
-		}
-
-		/// <summary>
-		///   Initializes a new instance of <see cref="Button"/> using <see cref="LayoutStyle.Absolute"/> layout, based on the given text
-		/// </summary>
-		/// <remarks>
-		///   The width of the <see cref="Button"/> is computed based on the
-		///   text length. The height will always be 1.
-		/// </remarks>
-		/// <param name="x">X position where the button will be shown.</param>
-		/// <param name="y">Y position where the button will be shown.</param>
-		/// <param name="text">The button's text</param>
-		public Button (int x, int y, string text) : this (x, y, text, false) { }
+	public Button () : this (text: string.Empty, is_default: false) { }
 
-		/// <summary>
-		///   Initializes a new instance of <see cref="Button"/> using <see cref="LayoutStyle.Absolute"/> layout, based on the given text.
-		/// </summary>
-		/// <remarks>
-		///   The width of the <see cref="Button"/> is computed based on the
-		///   text length. The height will always be 1.
-		/// </remarks>
-		/// <param name="x">X position where the button will be shown.</param>
-		/// <param name="y">Y position where the button will be shown.</param>
-		/// <param name="text">The button's text</param>
-		/// <param name="is_default">
-		///   If <c>true</c>, a special decoration is used, and the user pressing the enter key 
-		///   in a <see cref="Dialog"/> will implicitly activate this button.
-		/// </param>
-		public Button (int x, int y, string text, bool is_default)
-		    : base (new Rect (x, y, text.GetRuneCount () + 4 + (is_default ? 2 : 0), 1), text)
-		{
-			SetInitialProperties (text, is_default);
-		}
-		// TODO: v2 - Remove constructors with parameters
-		/// <summary>
-		/// Private helper to set the initial properties of the View that were provided via constructors.
-		/// </summary>
-		/// <param name="text"></param>
-		/// <param name="is_default"></param>
-		void SetInitialProperties (string text, bool is_default)
-		{
-			TextAlignment = TextAlignment.Centered;
-			VerticalTextAlignment = VerticalTextAlignment.Middle;
+	/// <summary>
+	///   Initializes a new instance of <see cref="Button"/> using <see cref="LayoutStyle.Computed"/> layout.
+	/// </summary>
+	/// <remarks>
+	///   The width of the <see cref="Button"/> is computed based on the
+	///   text length. The height will always be 1.
+	/// </remarks>
+	/// <param name="text">The button's text</param>
+	/// <param name="is_default">
+	///   If <c>true</c>, a special decoration is used, and the user pressing the enter key 
+	///   in a <see cref="Dialog"/> will implicitly activate this button.
+	/// </param>
+	public Button (string text, bool is_default = false) : base (text)
+	{
+		SetInitialProperties (text, is_default);
+	}
 
-			HotKeySpecifier = new Rune ('_');
+	/// <summary>
+	///   Initializes a new instance of <see cref="Button"/> using <see cref="LayoutStyle.Absolute"/> layout, based on the given text
+	/// </summary>
+	/// <remarks>
+	///   The width of the <see cref="Button"/> is computed based on the
+	///   text length. The height will always be 1.
+	/// </remarks>
+	/// <param name="x">X position where the button will be shown.</param>
+	/// <param name="y">Y position where the button will be shown.</param>
+	/// <param name="text">The button's text</param>
+	public Button (int x, int y, string text) : this (x, y, text, false) { }
 
-			_leftBracket = CM.Glyphs.LeftBracket;
-			_rightBracket = CM.Glyphs.RightBracket;
-			_leftDefault = CM.Glyphs.LeftDefaultIndicator;
-			_rightDefault = CM.Glyphs.RightDefaultIndicator;
+	/// <summary>
+	///   Initializes a new instance of <see cref="Button"/> using <see cref="LayoutStyle.Absolute"/> layout, based on the given text.
+	/// </summary>
+	/// <remarks>
+	///   The width of the <see cref="Button"/> is computed based on the
+	///   text length. The height will always be 1.
+	/// </remarks>
+	/// <param name="x">X position where the button will be shown.</param>
+	/// <param name="y">Y position where the button will be shown.</param>
+	/// <param name="text">The button's text</param>
+	/// <param name="is_default">
+	///   If <c>true</c>, a special decoration is used, and the user pressing the enter key 
+	///   in a <see cref="Dialog"/> will implicitly activate this button.
+	/// </param>
+	public Button (int x, int y, string text, bool is_default)
+	    : base (new Rect (x, y, text.GetRuneCount () + 4 + (is_default ? 2 : 0), 1), text)
+	{
+		SetInitialProperties (text, is_default);
+	}
 
-			CanFocus = true;
-			AutoSize = true;
-			this.is_default = is_default;
-			Text = text ?? string.Empty;
+	// TODO: v2 - Remove constructors with parameters
+	/// <summary>
+	/// Private helper to set the initial properties of the View that were provided via constructors.
+	/// </summary>
+	/// <param name="text"></param>
+	/// <param name="is_default"></param>
+	void SetInitialProperties (string text, bool is_default)
+	{
+		TextAlignment = TextAlignment.Centered;
+		VerticalTextAlignment = VerticalTextAlignment.Middle;
+
+		HotKeySpecifier = new Rune ('_');
+
+		_leftBracket = CM.Glyphs.LeftBracket;
+		_rightBracket = CM.Glyphs.RightBracket;
+		_leftDefault = CM.Glyphs.LeftDefaultIndicator;
+		_rightDefault = CM.Glyphs.RightDefaultIndicator;
+
+		CanFocus = true;
+		AutoSize = true;
+		_isDefault = is_default;
+		Text = text ?? string.Empty;
+
+		OnResizeNeeded ();
+
+		// Override default behavior of View
+		// Command.Default sets focus
+		AddCommand (Command.Accept, () => { OnClicked (); return true; });
+		KeyBindings.Add (Key.Space, Command.Default, Command.Accept);
+	}
 
+	/// <summary>
+	/// Gets or sets whether the <see cref="Button"/> is the default action to activate in a dialog.
+	/// </summary>
+	/// <value><c>true</c> if is default; otherwise, <c>false</c>.</value>
+	public bool IsDefault {
+		get => _isDefault;
+		set {
+			_isDefault = value;
+			UpdateTextFormatterText ();
 			OnResizeNeeded ();
-
-			// Things this view knows how to do
-			AddCommand (Command.Accept, () => AcceptKey ());
-
-			// Default keybindings for this view
-			AddKeyBinding (Key.Enter, Command.Accept);
-			AddKeyBinding (Key.Space, Command.Accept);
-			if (HotKey != Key.Null) {
-				AddKeyBinding (Key.Space | HotKey, Command.Accept);
-			}
 		}
+	}
 
-		/// <summary>
-		/// Gets or sets whether the <see cref="Button"/> is the default action to activate in a dialog.
-		/// </summary>
-		/// <value><c>true</c> if is default; otherwise, <c>false</c>.</value>
-		public bool IsDefault {
-			get => is_default;
-			set {
-				is_default = value;
-				UpdateTextFormatterText ();
-				OnResizeNeeded ();
-			}
-		}
+	/// <summary>
+	/// 
+	/// </summary>
+	public bool NoDecorations { get; set; }
 
-		/// <inheritdoc/>
-		public override Key HotKey {
-			get => base.HotKey;
-			set {
-				if (base.HotKey != value) {
-					var v = value == Key.Unknown ? Key.Null : value;
-					if (base.HotKey != Key.Null && ContainsKeyBinding (Key.Space | base.HotKey)) {
-						if (v == Key.Null) {
-							ClearKeyBinding (Key.Space | base.HotKey);
-						} else {
-							ReplaceKeyBinding (Key.Space | base.HotKey, Key.Space | v);
-						}
-					} else if (v != Key.Null) {
-						AddKeyBinding (Key.Space | v, Command.Accept);
-					}
-					base.HotKey = TextFormatter.HotKey = v;
-				}
+	/// <summary>
+	/// 
+	/// </summary>
+	public bool NoPadding { get; set; }
+
+	/// <inheritdoc/>
+	protected override void UpdateTextFormatterText ()
+	{
+		if (NoDecorations) {
+			TextFormatter.Text = Text;
+		} else
+		if (IsDefault)
+			TextFormatter.Text = $"{_leftBracket}{_leftDefault} {Text} {_rightDefault}{_rightBracket}";
+		else {
+			if (NoPadding) {
+				TextFormatter.Text = $"{_leftBracket}{Text}{_rightBracket}";
+			} else {
+				TextFormatter.Text = $"{_leftBracket} {Text} {_rightBracket}";
 			}
 		}
+	}
 
-		/// <summary>
-		/// 
-		/// </summary>
-		public bool NoDecorations { get; set; }
+	bool AcceptKey ()
+	{
+		//if (!HasFocus) {
+		//	SetFocus ();
+		//}
+		OnClicked ();
+		return true;
+	}
 
-		/// <summary>
-		/// 
-		/// </summary>
-		public bool NoPadding { get; set; }
+	/// <summary>
+	/// Virtual method to invoke the <see cref="Clicked"/> event.
+	/// </summary>
+	public virtual void OnClicked ()
+	{
+		Clicked?.Invoke (this, EventArgs.Empty);
+	}
 
-		/// <inheritdoc/>
-		protected override void UpdateTextFormatterText ()
-		{
-			if (NoDecorations) {
-				TextFormatter.Text = Text;
-			} else
-			if (IsDefault)
-				TextFormatter.Text = $"{_leftBracket}{_leftDefault} {Text} {_rightDefault}{_rightBracket}";
-			else {
-				if (NoPadding) {
-					TextFormatter.Text = $"{_leftBracket}{Text}{_rightBracket}";
-				} else {
-					TextFormatter.Text = $"{_leftBracket} {Text} {_rightBracket}";
+	/// <summary>
+	///   The event fired when the user clicks the primary mouse button within the Bounds of this <see cref="View"/>
+	///   or if the user presses the action key while this view is focused. (TODO: IsDefault)
+	/// </summary>
+	/// <remarks>
+	///   Client code can hook up to this event, it is
+	///   raised when the button is activated either with
+	///   the mouse or the keyboard.
+	/// </remarks>
+	public event EventHandler Clicked;
+
+	///<inheritdoc/>
+	public override bool MouseEvent (MouseEvent me)
+	{
+		if (me.Flags == MouseFlags.Button1Clicked) {
+			if (CanFocus && Enabled) {
+				if (!HasFocus) {
+					SetFocus ();
+					SetNeedsDisplay ();
+					Draw ();
 				}
+				OnClicked ();
 			}
-		}
-
-		///<inheritdoc/>
-		public override bool ProcessHotKey (KeyEvent kb)
-		{
-			if (!Enabled) {
-				return false;
-			}
-
-			return ExecuteHotKey (kb);
-		}
-
-		///<inheritdoc/>
-		public override bool ProcessColdKey (KeyEvent kb)
-		{
-			if (!Enabled) {
-				return false;
-			}
-
-			return ExecuteColdKey (kb);
-		}
-
-		///<inheritdoc/>
-		public override bool ProcessKey (KeyEvent kb)
-		{
-			if (!Enabled) {
-				return false;
-			}
-
-			var result = InvokeKeybindings (kb);
-			if (result != null)
-				return (bool)result;
-
-			return base.ProcessKey (kb);
-		}
-
-		bool ExecuteHotKey (KeyEvent ke)
-		{
-			if (ke.Key == (Key.AltMask | HotKey)) {
-				return AcceptKey ();
-			}
-			return false;
-		}
-
-		bool ExecuteColdKey (KeyEvent ke)
-		{
-			if (IsDefault && ke.KeyValue == '\n') {
-				return AcceptKey ();
-			}
-			return ExecuteHotKey (ke);
-		}
 
-		bool AcceptKey ()
-		{
-			if (!HasFocus) {
-				SetFocus ();
-			}
-			OnClicked ();
 			return true;
 		}
+		return false;
+	}
 
-		/// <summary>
-		/// Virtual method to invoke the <see cref="Clicked"/> event.
-		/// </summary>
-		public virtual void OnClicked ()
-		{
-			Clicked?.Invoke (this, EventArgs.Empty);
-		}
-
-		/// <summary>
-		///   The event fired when the user clicks the primary mouse button within the Bounds of this <see cref="View"/>
-		///   or if the user presses the action key while this view is focused. (TODO: IsDefault)
-		/// </summary>
-		/// <remarks>
-		///   Client code can hook up to this event, it is
-		///   raised when the button is activated either with
-		///   the mouse or the keyboard.
-		/// </remarks>
-		public event EventHandler Clicked;
-
-		///<inheritdoc/>
-		public override bool MouseEvent (MouseEvent me)
-		{
-			if (me.Flags == MouseFlags.Button1Clicked) {
-				if (CanFocus && Enabled) {
-					if (!HasFocus) {
-						SetFocus ();
-						SetNeedsDisplay ();
-						Draw ();
-					}
-					OnClicked ();
-				}
-
-				return true;
-			}
-			return false;
-		}
-
-		///<inheritdoc/>
-		public override void PositionCursor ()
-		{
-			if (HotKey == Key.Unknown && Text != "") {
-				for (int i = 0; i < TextFormatter.Text.GetRuneCount (); i++) {
-					if (TextFormatter.Text [i] == Text [0]) {
-						Move (i, 0);
-						return;
-					}
+	///<inheritdoc/>
+	public override void PositionCursor ()
+	{
+		if (HotKey.IsValid && Text != "") {
+			for (int i = 0; i < TextFormatter.Text.GetRuneCount (); i++) {
+				if (TextFormatter.Text [i] == Text [0]) {
+					Move (i, 0);
+					return;
 				}
 			}
-			base.PositionCursor ();
 		}
+		base.PositionCursor ();
+	}
 
-		///<inheritdoc/>
-		public override bool OnEnter (View view)
-		{
-			Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
+	///<inheritdoc/>
+	public override bool OnEnter (View view)
+	{
+		Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
 
-			return base.OnEnter (view);
-		}
+		return base.OnEnter (view);
 	}
 }

+ 205 - 197
Terminal.Gui/Views/CheckBox.cs

@@ -1,235 +1,243 @@
-//
-// Checkbox.cs: Checkbox control
-//
-// Authors:
-//   Miguel de Icaza ([email protected])
-//
-using System;
+using System;
 using System.Text;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui;
+
+/// <summary>
+/// The <see cref="CheckBox"/> <see cref="View"/> shows an on/off toggle that the user can set
+/// </summary>
+public class CheckBox : View {
+	Rune _charNullChecked;
+	Rune _charChecked;
+	Rune _charUnChecked;
+	bool? @_checked;
+	bool _allowNullChecked;
 
 	/// <summary>
-	/// The <see cref="CheckBox"/> <see cref="View"/> shows an on/off toggle that the user can set
+	///   Toggled event, raised when the <see cref="CheckBox"/>  is toggled.
 	/// </summary>
-	public class CheckBox : View {
-		Rune charNullChecked;
-		Rune charChecked;
-		Rune charUnChecked;
-		bool? @checked;
-		bool allowNullChecked;
-
-		/// <summary>
-		///   Toggled event, raised when the <see cref="CheckBox"/>  is toggled.
-		/// </summary>
-		/// <remarks>
-		///   Client code can hook up to this event, it is
-		///   raised when the <see cref="CheckBox"/> is activated either with
-		///   the mouse or the keyboard. The passed <c>bool</c> contains the previous state. 
-		/// </remarks>
-		public event EventHandler<ToggleEventArgs> Toggled;
-
-		/// <summary>
-		/// Called when the <see cref="Checked"/> property changes. Invokes the <see cref="Toggled"/> event.
-		/// </summary>
-		public virtual void OnToggled (ToggleEventArgs e)
-		{
-			Toggled?.Invoke (this, e);
-		}
+	/// <remarks>
+	///   Client code can hook up to this event, it is
+	///   raised when the <see cref="CheckBox"/> is activated either with
+	///   the mouse or the keyboard. The passed <c>bool</c> contains the previous state. 
+	/// </remarks>
+	public event EventHandler<ToggleEventArgs> Toggled;
 
-		/// <summary>
-		/// Initializes a new instance of <see cref="CheckBox"/> based on the given text, using <see cref="LayoutStyle.Computed"/> layout.
-		/// </summary>
-		public CheckBox () : this (string.Empty) { }
-
-		/// <summary>
-		/// Initializes a new instance of <see cref="CheckBox"/> based on the given text, using <see cref="LayoutStyle.Computed"/> layout.
-		/// </summary>
-		/// <param name="s">S.</param>
-		/// <param name="is_checked">If set to <c>true</c> is checked.</param>
-		public CheckBox (string s, bool is_checked = false) : base ()
-		{
-			SetInitialProperties (s, is_checked);
-		}
-
-		/// <summary>
-		/// Initializes a new instance of <see cref="CheckBox"/> using <see cref="LayoutStyle.Absolute"/> layout.
-		/// </summary>
-		/// <remarks>
-		///   The size of <see cref="CheckBox"/> is computed based on the
-		///   text length. This <see cref="CheckBox"/> is not toggled.
-		/// </remarks>
-		public CheckBox (int x, int y, string s) : this (x, y, s, false)
-		{
-		}
-
-		/// <summary>
-		/// Initializes a new instance of <see cref="CheckBox"/> using <see cref="LayoutStyle.Absolute"/> layout.
-		/// </summary>
-		/// <remarks>
-		///   The size of <see cref="CheckBox"/> is computed based on the
-		///   text length. 
-		/// </remarks>
-		public CheckBox (int x, int y, string s, bool is_checked) : base (new Rect (x, y, s.Length, 1))
-		{
-			SetInitialProperties (s, is_checked);
-		}
+	/// <summary>
+	/// Called when the <see cref="Checked"/> property changes. Invokes the <see cref="Toggled"/> event.
+	/// </summary>
+	public virtual void OnToggled (ToggleEventArgs e)
+	{
+		Toggled?.Invoke (this, e);
+	}
 
-		// TODO: v2 - Remove constructors with parameters
-		/// <summary>
-		/// Private helper to set the initial properties of the View that were provided via constructors.
-		/// </summary>
-		/// <param name="s"></param>
-		/// <param name="is_checked"></param>
-		void SetInitialProperties (string s, bool is_checked)
-		{
-			charNullChecked = CM.Glyphs.NullChecked;
-			charChecked = CM.Glyphs.Checked;
-			charUnChecked = CM.Glyphs.UnChecked;
-			Checked = is_checked;
-			HotKeySpecifier = (Rune)'_';
-			CanFocus = true;
-			AutoSize = true;
-			Text = s;
+	/// <summary>
+	/// Initializes a new instance of <see cref="CheckBox"/> based on the given text, using <see cref="LayoutStyle.Computed"/> layout.
+	/// </summary>
+	public CheckBox () : this (string.Empty) { }
 
-			OnResizeNeeded ();
+	/// <summary>
+	/// Initializes a new instance of <see cref="CheckBox"/> based on the given text, using <see cref="LayoutStyle.Computed"/> layout.
+	/// </summary>
+	/// <param name="s">S.</param>
+	/// <param name="is_checked">If set to <c>true</c> is checked.</param>
+	public CheckBox (string s, bool is_checked = false) : base ()
+	{
+		SetInitialProperties (s, is_checked);
+	}
 
-			// Things this view knows how to do
-			AddCommand (Command.ToggleChecked, () => ToggleChecked ());
+	/// <summary>
+	/// Initializes a new instance of <see cref="CheckBox"/> using <see cref="LayoutStyle.Absolute"/> layout.
+	/// </summary>
+	/// <remarks>
+	///   The size of <see cref="CheckBox"/> is computed based on the
+	///   text length. This <see cref="CheckBox"/> is not toggled.
+	/// </remarks>
+	public CheckBox (int x, int y, string s) : this (x, y, s, false)
+	{
+	}
 
-			// Default keybindings for this view
-			AddKeyBinding ((Key)' ', Command.ToggleChecked);
-			AddKeyBinding (Key.Space, Command.ToggleChecked);
-		}
+	/// <summary>
+	/// Initializes a new instance of <see cref="CheckBox"/> using <see cref="LayoutStyle.Absolute"/> layout.
+	/// </summary>
+	/// <remarks>
+	///   The size of <see cref="CheckBox"/> is computed based on the
+	///   text length. 
+	/// </remarks>
+	public CheckBox (int x, int y, string s, bool is_checked) : base (new Rect (x, y, s.Length, 1))
+	{
+		SetInitialProperties (s, is_checked);
+	}
 
-		/// <inheritdoc/>
-		protected override void UpdateTextFormatterText ()
-		{
-			switch (TextAlignment) {
-			case TextAlignment.Left:
-			case TextAlignment.Centered:
-			case TextAlignment.Justified:
-				TextFormatter.Text = $"{GetCheckedState ()} {GetFormatterText ()}";
-				break;
-			case TextAlignment.Right:
-				TextFormatter.Text = $"{GetFormatterText ()} {GetCheckedState ()}";
-				break;
+	// TODO: v2 - Remove constructors with parameters
+	/// <summary>
+	/// Private helper to set the initial properties of the View that were provided via constructors.
+	/// </summary>
+	/// <param name="s"></param>
+	/// <param name="is_checked"></param>
+	void SetInitialProperties (string s, bool is_checked)
+	{
+		_charNullChecked = CM.Glyphs.NullChecked;
+		_charChecked = CM.Glyphs.Checked;
+		_charUnChecked = CM.Glyphs.UnChecked;
+		Checked = is_checked;
+		HotKeySpecifier = (Rune)'_';
+		CanFocus = true;
+		AutoSize = true;
+		Text = s;
+
+		OnResizeNeeded ();
+
+		// Things this view knows how to do
+		AddCommand (Command.ToggleChecked, () => ToggleChecked ());
+		AddCommand (Command.Accept, () => {
+			if (!HasFocus) {
+				SetFocus ();
 			}
-		}
+			ToggleChecked ();
+			return true;
+		});
 
-		Rune GetCheckedState ()
-		{
-			return Checked switch {
-				true => charChecked,
-				false => charUnChecked,
-				var _ => charNullChecked
-			};
-		}
+		// Default keybindings for this view
+		KeyBindings.Add (Key.Space, Command.ToggleChecked);
+	}
 
-		string GetFormatterText ()
-		{
-			if (AutoSize || string.IsNullOrEmpty (Text) || Frame.Width <= 2) {
-				return Text;
+
+	/// <inheritdoc/>
+	public override Key HotKey {
+		get => base.HotKey;
+		set {
+			if (value is null || value.KeyCode is KeyCode.Unknown) {
+				throw new ArgumentException (nameof (value));
 			}
-			return Text [..Math.Min (Frame.Width - 2, Text.GetRuneCount ())];
-		}
 
-		/// <summary>
-		///    The state of the <see cref="CheckBox"/>
-		/// </summary>
-		public bool? Checked {
-			get => @checked;
-			set {
-				if (value == null && !AllowNullChecked) {
-					return;
+			var prev = base.HotKey;
+			if (prev != value) {
+				var v = value == KeyCode.Unknown ? Key.Empty: value;
+				base.HotKey = TextFormatter.HotKey = v;
+
+				// Also add Alt+HotKey
+				if (prev != (Key)KeyCode.Null && KeyBindings.TryGet (prev.WithAlt, out _)) {
+					if (v.KeyCode == KeyCode.Null) {
+						KeyBindings.Remove (prev.WithAlt);
+					} else {
+						KeyBindings.Replace (prev.WithAlt, v.WithAlt);
+					}
+				} else if (v.KeyCode != KeyCode.Null) {
+					KeyBindings.Add (v.WithAlt, Command.Accept);
 				}
-				@checked = value;
-				UpdateTextFormatterText ();
-				OnResizeNeeded ();
 			}
 		}
+	}
 
-		/// <summary>
-		/// If <see langword="true"/> allows <see cref="Checked"/> to be null, true or false.
-		/// If <see langword="false"/> only allows <see cref="Checked"/> to be true or false.
-		/// </summary>
-		public bool AllowNullChecked {
-			get => allowNullChecked;
-			set {
-				allowNullChecked = value;
-				Checked ??= false;
-			}
+	/// <inheritdoc/>
+	protected override void UpdateTextFormatterText ()
+	{
+		switch (TextAlignment) {
+		case TextAlignment.Left:
+		case TextAlignment.Centered:
+		case TextAlignment.Justified:
+			TextFormatter.Text = $"{GetCheckedState ()} {GetFormatterText ()}";
+			break;
+		case TextAlignment.Right:
+			TextFormatter.Text = $"{GetFormatterText ()} {GetCheckedState ()}";
+			break;
 		}
+	}
+
+	Rune GetCheckedState ()
+	{
+		return Checked switch {
+			true => _charChecked,
+			false => _charUnChecked,
+			var _ => _charNullChecked
+		};
+	}
 
-		///<inheritdoc/>
-		public override void PositionCursor ()
-		{
-			Move (0, 0);
+	string GetFormatterText ()
+	{
+		if (AutoSize || string.IsNullOrEmpty (Text) || Frame.Width <= 2) {
+			return Text;
 		}
+		return Text [..Math.Min (Frame.Width - 2, Text.GetRuneCount ())];
+	}
 
-		///<inheritdoc/>
-		public override bool ProcessKey (KeyEvent kb)
-		{
-			var result = InvokeKeybindings (kb);
-			if (result != null)
-				return (bool)result;
+	/// <summary>
+	///    The state of the <see cref="CheckBox"/>
+	/// </summary>
+	public bool? Checked {
+		get => @_checked;
+		set {
+			if (value == null && !AllowNullChecked) {
+				return;
+			}
+			@_checked = value;
+			UpdateTextFormatterText ();
+			OnResizeNeeded ();
+		}
+	}
 
-			return base.ProcessKey (kb);
+	/// <summary>
+	/// If <see langword="true"/> allows <see cref="Checked"/> to be null, true or false.
+	/// If <see langword="false"/> only allows <see cref="Checked"/> to be true or false.
+	/// </summary>
+	public bool AllowNullChecked {
+		get => _allowNullChecked;
+		set {
+			_allowNullChecked = value;
+			Checked ??= false;
 		}
+	}
 
-		///<inheritdoc/>
-		public override bool ProcessHotKey (KeyEvent kb)
-		{
-			if (kb.Key == (Key.AltMask | HotKey))
-				return ToggleChecked ();
+	///<inheritdoc/>
+	public override void PositionCursor ()
+	{
+		Move (0, 0);
+	}
 
-			return false;
+	bool ToggleChecked ()
+	{
+		if (!HasFocus) {
+			SetFocus ();
 		}
-
-		bool ToggleChecked ()
-		{
-			if (!HasFocus) {
-				SetFocus ();
-			}
-			var previousChecked = Checked;
-			if (AllowNullChecked) {
-				switch (previousChecked) {
-				case null:
-					Checked = true;
-					break;
-				case true:
-					Checked = false;
-					break;
-				case false:
-					Checked = null;
-					break;
-				}
-			} else {
-				Checked = !Checked;
+		var previousChecked = Checked;
+		if (AllowNullChecked) {
+			switch (previousChecked) {
+			case null:
+				Checked = true;
+				break;
+			case true:
+				Checked = false;
+				break;
+			case false:
+				Checked = null;
+				break;
 			}
-
-			OnToggled (new ToggleEventArgs (previousChecked, Checked));
-			SetNeedsDisplay ();
-			return true;
+		} else {
+			Checked = !Checked;
 		}
 
-		///<inheritdoc/>
-		public override bool MouseEvent (MouseEvent me)
-		{
-			if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) || !CanFocus)
-				return false;
+		OnToggled (new ToggleEventArgs (previousChecked, Checked));
+		SetNeedsDisplay ();
+		return true;
+	}
 
-			ToggleChecked ();
+	///<inheritdoc/>
+	public override bool MouseEvent (MouseEvent me)
+	{
+		if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) || !CanFocus)
+			return false;
 
-			return true;
-		}
+		ToggleChecked ();
+
+		return true;
+	}
 
-		///<inheritdoc/>
-		public override bool OnEnter (View view)
-		{
-			Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
+	///<inheritdoc/>
+	public override bool OnEnter (View view)
+	{
+		Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
 
-			return base.OnEnter (view);
-		}
+		return base.OnEnter (view);
 	}
 }

+ 4 - 14
Terminal.Gui/Views/ColorPicker.cs

@@ -143,10 +143,10 @@ namespace Terminal.Gui {
 		/// </summary>
 		private void AddKeyBindings ()
 		{
-			AddKeyBinding (Key.CursorLeft, Command.Left);
-			AddKeyBinding (Key.CursorRight, Command.Right);
-			AddKeyBinding (Key.CursorUp, Command.LineUp);
-			AddKeyBinding (Key.CursorDown, Command.LineDown);
+			KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
+			KeyBindings.Add (KeyCode.CursorRight, Command.Right);
+			KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
+			KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
 		}
 
 		///<inheritdoc/>
@@ -250,16 +250,6 @@ namespace Terminal.Gui {
 			return true;
 		}
 
-		///<inheritdoc/>
-		public override bool ProcessKey (KeyEvent kb)
-		{
-			var result = InvokeKeybindings (kb);
-			if (result != null)
-				return (bool)result;
-
-			return false;
-		}
-
 		///<inheritdoc/>
 		public override bool MouseEvent (MouseEvent me)
 		{

+ 10 - 20
Terminal.Gui/Views/ComboBox.cs

@@ -343,16 +343,16 @@ namespace Terminal.Gui {
 			AddCommand (Command.UnixEmulation, () => UnixEmulation ());
 
 			// Default keybindings for this view
-			AddKeyBinding (Key.Enter, Command.Accept);
-			AddKeyBinding (Key.F4, Command.ToggleExpandCollapse);
-			AddKeyBinding (Key.CursorDown, Command.LineDown);
-			AddKeyBinding (Key.CursorUp, Command.LineUp);
-			AddKeyBinding (Key.PageDown, Command.PageDown);
-			AddKeyBinding (Key.PageUp, Command.PageUp);
-			AddKeyBinding (Key.Home, Command.TopHome);
-			AddKeyBinding (Key.End, Command.BottomEnd);
-			AddKeyBinding (Key.Esc, Command.Cancel);
-			AddKeyBinding (Key.U | Key.CtrlMask, Command.UnixEmulation);
+			KeyBindings.Add (KeyCode.Enter, Command.Accept);
+			KeyBindings.Add (KeyCode.F4, Command.ToggleExpandCollapse);
+			KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
+			KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
+			KeyBindings.Add (KeyCode.PageDown, Command.PageDown);
+			KeyBindings.Add (KeyCode.PageUp, Command.PageUp);
+			KeyBindings.Add (KeyCode.Home, Command.TopHome);
+			KeyBindings.Add (KeyCode.End, Command.BottomEnd);
+			KeyBindings.Add (KeyCode.Esc, Command.Cancel);
+			KeyBindings.Add (KeyCode.U | KeyCode.CtrlMask, Command.UnixEmulation);
 		}
 
 		private bool isShow = false;
@@ -544,16 +544,6 @@ namespace Terminal.Gui {
 			Driver.AddRune (CM.Glyphs.DownArrow);
 		}
 
-		///<inheritdoc/>
-		public override bool ProcessKey (KeyEvent e)
-		{
-			var result = InvokeKeybindings (e);
-			if (result != null)
-				return (bool)result;
-
-			return base.ProcessKey (e);
-		}
-
 		bool UnixEmulation ()
 		{
 			// Unix emulation

+ 0 - 234
Terminal.Gui/Views/ContextMenu.cs

@@ -1,234 +0,0 @@
-using System;
-
-namespace Terminal.Gui {
-	/// <summary>
-	/// ContextMenu provides a pop-up menu that can be positioned anywhere within a <see cref="View"/>. 
-	/// ContextMenu is analogous to <see cref="MenuBar"/> and, once activated, works like a sub-menu 
-	/// of a <see cref="MenuBarItem"/> (but can be positioned anywhere).
-	/// <para>
-	/// By default, a ContextMenu with sub-menus is displayed in a cascading manner, where each sub-menu pops out of the ContextMenu frame
-	/// (either to the right or left, depending on where the ContextMenu is relative to the edge of the screen). By setting
-	/// <see cref="UseSubMenusSingleFrame"/> to <see langword="true"/>, this behavior can be changed such that all sub-menus are
-	/// drawn within the ContextMenu frame.
-	/// </para>
-	/// <para>
-	/// ContextMenus can be activated using the Shift-F10 key (by default; use the <see cref="Key"/> to change to another key).
-	/// </para>
-	/// <para>
-	/// Callers can cause the ContextMenu to be activated on a right-mouse click (or other interaction) by calling <see cref="Show()"/>.
-	/// </para>
-	/// <para>
-	/// ContextMenus are located using screen using screen coordinates and appear above all other Views.
-	/// </para>
-	/// </summary>
-	public sealed class ContextMenu : IDisposable {
-		private static MenuBar menuBar;
-		private Key key = Key.F10 | Key.ShiftMask;
-		private MouseFlags mouseFlags = MouseFlags.Button3Clicked;
-		private Toplevel container;
-
-		/// <summary>
-		/// Initializes a context menu with no menu items.
-		/// </summary>
-		public ContextMenu () : this (0, 0, new MenuBarItem ()) { }
-
-		/// <summary>
-		/// Initializes a context menu, with a <see cref="View"/> specifying the parent/host of the menu.
-		/// </summary>
-		/// <param name="host">The host view.</param>
-		/// <param name="menuItems">The menu items for the context menu.</param>
-		public ContextMenu (View host, MenuBarItem menuItems) :
-			this (host.Frame.X, host.Frame.Y, menuItems)
-		{
-			Host = host;
-		}
-
-		/// <summary>
-		/// Initializes a context menu with menu items at a specific screen location.
-		/// </summary>
-		/// <param name="x">The left position (screen relative).</param>
-		/// <param name="y">The top position (screen relative).</param>
-		/// <param name="menuItems">The menu items.</param>
-		public ContextMenu (int x, int y, MenuBarItem menuItems)
-		{
-			if (IsShow) {
-				if (menuBar.SuperView != null) {
-					Hide ();
-				}
-				IsShow = false;
-			}
-			MenuItems = menuItems;
-			Position = new Point (x, y);
-		}
-
-		private void MenuBar_MenuAllClosed (object sender, EventArgs e)
-		{
-			Dispose ();
-		}
-
-		/// <summary>
-		/// Disposes the context menu object.
-		/// </summary>
-		public void Dispose ()
-		{
-			if (IsShow) {
-				menuBar.MenuAllClosed -= MenuBar_MenuAllClosed;
-				menuBar.Dispose ();
-				menuBar = null;
-				IsShow = false;
-			}
-			if (container != null) {
-				container.Closing -= Container_Closing;
-			}
-		}
-
-		/// <summary>
-		/// Shows (opens) the ContextMenu, displaying the <see cref="MenuItem"/>s it contains.
-		/// </summary>
-		public void Show ()
-		{
-			if (menuBar != null) {
-				Hide ();
-			}
-			container = Application.Current;
-			container.Closing += Container_Closing;
-			var frame = new Rect (0, 0, View.Driver.Cols, View.Driver.Rows);
-			var position = Position;
-			if (Host != null) {
-				Host.BoundsToScreen (frame.X, frame.Y, out int x, out int y);
-				var pos = new Point (x, y);
-				pos.Y += Host.Frame.Height - 1;
-				if (position != pos) {
-					Position = position = pos;
-				}
-			}
-			var rect = Menu.MakeFrame (position.X, position.Y, MenuItems.Children);
-			if (rect.Right >= frame.Right) {
-				if (frame.Right - rect.Width >= 0 || !ForceMinimumPosToZero) {
-					position.X = frame.Right - rect.Width;
-				} else if (ForceMinimumPosToZero) {
-					position.X = 0;
-				}
-			} else if (ForceMinimumPosToZero && position.X < 0) {
-				position.X = 0;
-			}
-			if (rect.Bottom >= frame.Bottom) {
-				if (frame.Bottom - rect.Height - 1 >= 0 || !ForceMinimumPosToZero) {
-					if (Host == null) {
-						position.Y = frame.Bottom - rect.Height - 1;
-					} else {
-						Host.BoundsToScreen (frame.X, frame.Y, out int x, out int y);
-						var pos = new Point (x, y);
-						position.Y = pos.Y - rect.Height - 1;
-					}
-				} else if (ForceMinimumPosToZero) {
-					position.Y = 0;
-				}
-			} else if (ForceMinimumPosToZero && position.Y < 0) {
-				position.Y = 0;
-			}
-
-			menuBar = new MenuBar (new [] { MenuItems }) {
-				X = position.X,
-				Y = position.Y,
-				Width = 0,
-				Height = 0,
-				UseSubMenusSingleFrame = UseSubMenusSingleFrame,
-				Key = Key
-			};
-
-			menuBar.isContextMenuLoading = true;
-			menuBar.MenuAllClosed += MenuBar_MenuAllClosed;
-			IsShow = true;
-			menuBar.OpenMenu ();
-		}
-
-		private void Container_Closing (object sender, ToplevelClosingEventArgs obj)
-		{
-			Hide ();
-		}
-
-		/// <summary>
-		/// Hides (closes) the ContextMenu.
-		/// </summary>
-		public void Hide ()
-		{
-			menuBar?.CleanUp ();
-			Dispose ();
-		}
-
-		/// <summary>
-		/// Event invoked when the <see cref="ContextMenu.Key"/> is changed.
-		/// </summary>
-		public event EventHandler<KeyChangedEventArgs> KeyChanged;
-
-		/// <summary>
-		/// Event invoked when the <see cref="ContextMenu.MouseFlags"/> is changed.
-		/// </summary>
-		public event EventHandler<MouseFlagsChangedEventArgs> MouseFlagsChanged;
-
-		/// <summary>
-		/// Gets or sets the menu position.
-		/// </summary>
-		public Point Position { get; set; }
-
-		/// <summary>
-		/// Gets or sets the menu items for this context menu.
-		/// </summary>
-		public MenuBarItem MenuItems { get; set; }
-
-		/// <summary>
-		/// <see cref="Gui.Key"/> specifies they keyboard key that will activate the context menu with the keyboard.
-		/// </summary>
-		public Key Key {
-			get => key;
-			set {
-				var oldKey = key;
-				key = value;
-				KeyChanged?.Invoke (this, new KeyChangedEventArgs (oldKey, key));
-			}
-		}
-
-		/// <summary>
-		/// <see cref="Gui.MouseFlags"/> specifies the mouse action used to activate the context menu by mouse.
-		/// </summary>
-		public MouseFlags MouseFlags {
-			get => mouseFlags;
-			set {
-				var oldFlags = mouseFlags;
-				mouseFlags = value;
-				MouseFlagsChanged?.Invoke (this, new MouseFlagsChangedEventArgs (oldFlags, value));
-			}
-		}
-
-		/// <summary>
-		/// Gets whether the ContextMenu is showing or not.
-		/// </summary>
-		public static bool IsShow { get; private set; }
-
-		/// <summary>
-		/// The host <see cref="View "/> which position will be used,
-		/// otherwise if it's null the container will be used.
-		/// </summary>
-		public View Host { get; set; }
-
-		/// <summary>
-		/// Sets or gets whether the context menu be forced to the right, ensuring it is not clipped, if the x position 
-		/// is less than zero. The default is <see langword="true"/> which means the context menu will be forced to the right.
-		/// If set to <see langword="false"/>, the context menu will be clipped on the left if x is less than zero.
-		/// </summary>
-		public bool ForceMinimumPosToZero { get; set; } = true;
-
-		/// <summary>
-		/// Gets the <see cref="Gui.MenuBar"/> that is hosting this context menu.
-		/// </summary>
-		public MenuBar MenuBar { get => menuBar; }
-
-		/// <summary>
-		/// Gets or sets if sub-menus will be displayed using a "single frame" menu style. If <see langword="true"/>, the ContextMenu
-		/// and any sub-menus that would normally cascade will be displayed within a single frame. If <see langword="false"/> (the default),
-		/// sub-menus will cascade using separate frames for each level of the menu hierarchy.
-		/// </summary>
-		public bool UseSubMenusSingleFrame { get; set; }
-	}
-}

+ 353 - 348
Terminal.Gui/Views/DateField.cs

@@ -10,419 +10,424 @@ using System.Globalization;
 using System.Linq;
 using System.Text;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui; 
+
+/// <summary>
+///   Simple Date editing <see cref="View"/>
+/// </summary>
+/// <remarks>
+///   The <see cref="DateField"/> <see cref="View"/> provides date editing functionality with mouse support.
+/// </remarks>
+public class DateField : TextField {
+	DateTime date;
+	bool isShort;
+	int longFieldLen = 10;
+	int shortFieldLen = 8;
+	string sepChar;
+	string longFormat;
+	string shortFormat;
+
+	int fieldLen => isShort ? shortFieldLen : longFieldLen;
+
+	string format => isShort ? shortFormat : longFormat;
+
 	/// <summary>
-	///   Simple Date editing <see cref="View"/>
+	///   DateChanged event, raised when the <see cref="Date"/> property has changed.
 	/// </summary>
 	/// <remarks>
-	///   The <see cref="DateField"/> <see cref="View"/> provides date editing functionality with mouse support.
+	///   This event is raised when the <see cref="Date"/> property changes.
 	/// </remarks>
-	public class DateField : TextField {
-		DateTime date;
-		bool isShort;
-		int longFieldLen = 10;
-		int shortFieldLen = 8;
-		string sepChar;
-		string longFormat;
-		string shortFormat;
-
-		int fieldLen => isShort ? shortFieldLen : longFieldLen;
-		string format => isShort ? shortFormat : longFormat;
-
-		/// <summary>
-		///   DateChanged event, raised when the <see cref="Date"/> property has changed.
-		/// </summary>
-		/// <remarks>
-		///   This event is raised when the <see cref="Date"/> property changes.
-		/// </remarks>
-		/// <remarks>
-		///   The passed event arguments containing the old value, new value, and format string.
-		/// </remarks>
-		public event EventHandler<DateTimeEventArgs<DateTime>> DateChanged;
-
-		/// <summary>
-		///    Initializes a new instance of <see cref="DateField"/> using <see cref="LayoutStyle.Absolute"/> layout.
-		/// </summary>
-		/// <param name="x">The x coordinate.</param>
-		/// <param name="y">The y coordinate.</param>
-		/// <param name="date">Initial date contents.</param>
-		/// <param name="isShort">If true, shows only two digits for the year.</param>
-		public DateField (int x, int y, DateTime date, bool isShort = false) : base (x, y, isShort ? 10 : 12, "")
-		{
-			Initialize (date, isShort);
-		}
+	/// <remarks>
+	///   The passed event arguments containing the old value, new value, and format string.
+	/// </remarks>
+	public event EventHandler<DateTimeEventArgs<DateTime>> DateChanged;
 
-		/// <summary>
-		///  Initializes a new instance of <see cref="DateField"/> using <see cref="LayoutStyle.Computed"/> layout.
-		/// </summary>
-		public DateField () : this (DateTime.MinValue) { }
-
-		/// <summary>
-		///  Initializes a new instance of <see cref="DateField"/> using <see cref="LayoutStyle.Computed"/> layout.
-		/// </summary>
-		/// <param name="date"></param>
-		public DateField (DateTime date) : base ("")
-		{
-			Width = fieldLen + 2;
-			Initialize (date);
-		}
+	/// <summary>
+	///    Initializes a new instance of <see cref="DateField"/> using <see cref="LayoutStyle.Absolute"/> layout.
+	/// </summary>
+	/// <param name="x">The x coordinate.</param>
+	/// <param name="y">The y coordinate.</param>
+	/// <param name="date">Initial date contents.</param>
+	/// <param name="isShort">If true, shows only two digits for the year.</param>
+	public DateField (int x, int y, DateTime date, bool isShort = false) : base (x, y, isShort ? 10 : 12, "") => Initialize (date, isShort);
+
+	/// <summary>
+	///  Initializes a new instance of <see cref="DateField"/> using <see cref="LayoutStyle.Computed"/> layout.
+	/// </summary>
+	public DateField () : this (DateTime.MinValue) { }
+
+	/// <summary>
+	///  Initializes a new instance of <see cref="DateField"/> using <see cref="LayoutStyle.Computed"/> layout.
+	/// </summary>
+	/// <param name="date"></param>
+	public DateField (DateTime date) : base ("")
+	{
+		Width = fieldLen + 2;
+		Initialize (date);
+	}
+
+	void Initialize (DateTime date, bool isShort = false)
+	{
+		var cultureInfo = CultureInfo.CurrentCulture;
+		sepChar = cultureInfo.DateTimeFormat.DateSeparator;
+		longFormat = GetLongFormat (cultureInfo.DateTimeFormat.ShortDatePattern);
+		shortFormat = GetShortFormat (longFormat);
+		this.isShort = isShort;
+		Date = date;
+		CursorPosition = 1;
+		TextChanged += DateField_Changed;
+
+		// Things this view knows how to do
+		AddCommand (Command.DeleteCharRight, () => {
+			DeleteCharRight ();
+			return true;
+		});
+		AddCommand (Command.DeleteCharLeft, () => {
+			DeleteCharLeft (false);
+			return true;
+		});
+		AddCommand (Command.LeftHome, () => MoveHome ());
+		AddCommand (Command.Left, () => MoveLeft ());
+		AddCommand (Command.RightEnd, () => MoveEnd ());
+		AddCommand (Command.Right, () => MoveRight ());
+
+		// Default keybindings for this view
+		KeyBindings.Add (KeyCode.DeleteChar, Command.DeleteCharRight);
+		KeyBindings.Add (Key.D.WithCtrl, Command.DeleteCharRight);
+
+		KeyBindings.Add (Key.Delete, Command.DeleteCharLeft);
+		KeyBindings.Add (Key.Backspace, Command.DeleteCharLeft);
+
+		KeyBindings.Add (Key.Home, Command.LeftHome);
+		KeyBindings.Add (Key.A.WithCtrl, Command.LeftHome);
 
-		void Initialize (DateTime date, bool isShort = false)
-		{
-			CultureInfo cultureInfo = CultureInfo.CurrentCulture;
-			sepChar = cultureInfo.DateTimeFormat.DateSeparator;
-			longFormat = GetLongFormat (cultureInfo.DateTimeFormat.ShortDatePattern);
-			shortFormat = GetShortFormat (longFormat);
-			this.isShort = isShort;
-			Date = date;
-			CursorPosition = 1;
-			TextChanged += DateField_Changed;
-
-			// Things this view knows how to do
-			AddCommand (Command.DeleteCharRight, () => { DeleteCharRight (); return true; });
-			AddCommand (Command.DeleteCharLeft, () => { DeleteCharLeft (); return true; });
-			AddCommand (Command.LeftHome, () => MoveHome ());
-			AddCommand (Command.Left, () => MoveLeft ());
-			AddCommand (Command.RightEnd, () => MoveEnd ());
-			AddCommand (Command.Right, () => MoveRight ());
-
-			// Default keybindings for this view
-			AddKeyBinding (Key.DeleteChar, Command.DeleteCharRight);
-			AddKeyBinding (Key.D | Key.CtrlMask, Command.DeleteCharRight);
-
-			AddKeyBinding (Key.Delete, Command.DeleteCharLeft);
-			AddKeyBinding (Key.Backspace, Command.DeleteCharLeft);
-
-			AddKeyBinding (Key.Home, Command.LeftHome);
-			AddKeyBinding (Key.A | Key.CtrlMask, Command.LeftHome);
-
-			AddKeyBinding (Key.CursorLeft, Command.Left);
-			AddKeyBinding (Key.B | Key.CtrlMask, Command.Left);
-
-			AddKeyBinding (Key.End, Command.RightEnd);
-			AddKeyBinding (Key.E | Key.CtrlMask, Command.RightEnd);
-
-			AddKeyBinding (Key.CursorRight, Command.Right);
-			AddKeyBinding (Key.F | Key.CtrlMask, Command.Right);
+		KeyBindings.Add (Key.CursorLeft, Command.Left);
+		KeyBindings.Add (Key.B.WithCtrl, Command.Left);
+
+		KeyBindings.Add (Key.End, Command.RightEnd);
+		KeyBindings.Add (Key.E.WithCtrl, Command.RightEnd);
+
+		KeyBindings.Add (Key.CursorRight, Command.Right);
+		KeyBindings.Add (Key.F.WithCtrl, Command.Right);
+
+	}
+
+	/// <inheritdoc />
+	public override bool OnProcessKeyDown (Key a)
+	{
+		// Ignore non-numeric characters.
+		if (a >= Key.D0 && a <= Key.D9) {
+			if (!ReadOnly) {
+				if (SetText ((Rune)a)) {
+					IncCursorPosition ();
+				}
+			}
+			return true;
 		}
+		return false;
+	}
 
-		void DateField_Changed (object sender, TextChangedEventArgs e)
-		{
-			try {
-				if (!DateTime.TryParseExact (GetDate (Text), GetInvarianteFormat (), CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result))
-					Text = e.OldValue;
-			} catch (Exception) {
+	void DateField_Changed (object sender, TextChangedEventArgs e)
+	{
+		try {
+			if (!DateTime.TryParseExact (GetDate (Text), GetInvarianteFormat (), CultureInfo.CurrentCulture, DateTimeStyles.None, out var result)) {
 				Text = e.OldValue;
 			}
+		} catch (Exception) {
+			Text = e.OldValue;
 		}
+	}
 
-		string GetInvarianteFormat ()
-		{
-			return $"MM{sepChar}dd{sepChar}yyyy";
-		}
+	string GetInvarianteFormat () => $"MM{sepChar}dd{sepChar}yyyy";
 
-		string GetLongFormat (string lf)
-		{
-			string [] frm = lf.Split (sepChar);
-			for (int i = 0; i < frm.Length; i++) {
-				if (frm [i].Contains ("M") && frm [i].GetRuneCount () < 2)
-					lf = lf.Replace ("M", "MM");
-				if (frm [i].Contains ("d") && frm [i].GetRuneCount () < 2)
-					lf = lf.Replace ("d", "dd");
-				if (frm [i].Contains ("y") && frm [i].GetRuneCount () < 4)
-					lf = lf.Replace ("yy", "yyyy");
+	string GetLongFormat (string lf)
+	{
+		string [] frm = lf.Split (sepChar);
+		for (int i = 0; i < frm.Length; i++) {
+			if (frm [i].Contains ("M") && frm [i].GetRuneCount () < 2) {
+				lf = lf.Replace ("M", "MM");
+			}
+			if (frm [i].Contains ("d") && frm [i].GetRuneCount () < 2) {
+				lf = lf.Replace ("d", "dd");
+			}
+			if (frm [i].Contains ("y") && frm [i].GetRuneCount () < 4) {
+				lf = lf.Replace ("yy", "yyyy");
 			}
-			return $" {lf}";
 		}
+		return $" {lf}";
+	}
 
-		string GetShortFormat (string lf)
-		{
-			return lf.Replace ("yyyy", "yy");
-		}
+	string GetShortFormat (string lf) => lf.Replace ("yyyy", "yy");
 
-		/// <summary>
-		///   Gets or sets the date of the <see cref="DateField"/>.
-		/// </summary>
-		/// <remarks>
-		/// </remarks>
-		public DateTime Date {
-			get {
-				return date;
+	/// <summary>
+	///   Gets or sets the date of the <see cref="DateField"/>.
+	/// </summary>
+	/// <remarks>
+	/// </remarks>
+	public DateTime Date {
+		get => date;
+		set {
+			if (ReadOnly) {
+				return;
 			}
-			set {
-				if (ReadOnly)
-					return;
-
-				var oldData = date;
-				date = value;
-				this.Text = value.ToString (format);
-				var args = new DateTimeEventArgs<DateTime> (oldData, value, format);
-				if (oldData != value) {
-					OnDateChanged (args);
-				}
+
+			var oldData = date;
+			date = value;
+			Text = value.ToString (format);
+			var args = new DateTimeEventArgs<DateTime> (oldData, value, format);
+			if (oldData != value) {
+				OnDateChanged (args);
 			}
 		}
+	}
 
-		/// <summary>
-		/// Get or set the date format for the widget.
-		/// </summary>
-		public bool IsShortFormat {
-			get => isShort;
-			set {
-				isShort = value;
-				if (isShort)
-					Width = 10;
-				else
-					Width = 12;
-				var ro = ReadOnly;
-				if (ro)
-					ReadOnly = false;
-				SetText (Text);
-				ReadOnly = ro;
-				SetNeedsDisplay ();
+	/// <summary>
+	/// Get or set the date format for the widget.
+	/// </summary>
+	public bool IsShortFormat {
+		get => isShort;
+		set {
+			isShort = value;
+			if (isShort) {
+				Width = 10;
+			} else {
+				Width = 12;
 			}
+			bool ro = ReadOnly;
+			if (ro) {
+				ReadOnly = false;
+			}
+			SetText (Text);
+			ReadOnly = ro;
+			SetNeedsDisplay ();
 		}
+	}
 
-		/// <inheritdoc/>
-		public override int CursorPosition {
-			get => base.CursorPosition;
-			set {
-				base.CursorPosition = Math.Max (Math.Min (value, fieldLen), 1);
-			}
+	/// <inheritdoc/>
+	public override int CursorPosition {
+		get => base.CursorPosition;
+		set => base.CursorPosition = Math.Max (Math.Min (value, fieldLen), 1);
+	}
+
+	bool SetText (Rune key)
+	{
+		var text = Text.EnumerateRunes ().ToList ();
+		var newText = text.GetRange (0, CursorPosition);
+		newText.Add (key);
+		if (CursorPosition < fieldLen) {
+			newText = newText.Concat (text.GetRange (CursorPosition + 1, text.Count - (CursorPosition + 1))).ToList ();
 		}
+		return SetText (StringExtensions.ToString (newText));
+	}
 
-		bool SetText (Rune key)
-		{
-			var text = Text.EnumerateRunes ().ToList ();
-			var newText = text.GetRange (0, CursorPosition);
-			newText.Add (key);
-			if (CursorPosition < fieldLen)
-				newText = newText.Concat (text.GetRange (CursorPosition + 1, text.Count - (CursorPosition + 1))).ToList ();
-			return SetText (StringExtensions.ToString (newText));
+	bool SetText (string text)
+	{
+		if (string.IsNullOrEmpty (text)) {
+			return false;
 		}
 
-		bool SetText (string text)
-		{
-			if (string.IsNullOrEmpty (text)) {
-				return false;
-			}
+		string [] vals = text.Split (sepChar);
+		string [] frm = format.Split (sepChar);
+		bool isValidDate = true;
+		int idx = GetFormatIndex (frm, "y");
+		int year = Int32.Parse (vals [idx]);
+		int month;
+		int day;
+		idx = GetFormatIndex (frm, "M");
+		if (Int32.Parse (vals [idx]) < 1) {
+			isValidDate = false;
+			month = 1;
+			vals [idx] = "1";
+		} else if (Int32.Parse (vals [idx]) > 12) {
+			isValidDate = false;
+			month = 12;
+			vals [idx] = "12";
+		} else {
+			month = Int32.Parse (vals [idx]);
+		}
+		idx = GetFormatIndex (frm, "d");
+		if (Int32.Parse (vals [idx]) < 1) {
+			isValidDate = false;
+			day = 1;
+			vals [idx] = "1";
+		} else if (Int32.Parse (vals [idx]) > 31) {
+			isValidDate = false;
+			day = DateTime.DaysInMonth (year, month);
+			vals [idx] = day.ToString ();
+		} else {
+			day = Int32.Parse (vals [idx]);
+		}
+		string d = GetDate (month, day, year, frm);
 
-			string [] vals = text.Split (sepChar);
-			string [] frm = format.Split (sepChar);
-			bool isValidDate = true;
-			int idx = GetFormatIndex (frm, "y");
-			int year = Int32.Parse (vals [idx]);
-			int month;
-			int day;
-			idx = GetFormatIndex (frm, "M");
-			if (Int32.Parse (vals [idx]) < 1) {
-				isValidDate = false;
-				month = 1;
-				vals [idx] = "1";
-			} else if (Int32.Parse (vals [idx]) > 12) {
-				isValidDate = false;
-				month = 12;
-				vals [idx] = "12";
-			} else
-				month = Int32.Parse (vals [idx]);
-			idx = GetFormatIndex (frm, "d");
-			if (Int32.Parse (vals [idx]) < 1) {
-				isValidDate = false;
-				day = 1;
-				vals [idx] = "1";
-			} else if (Int32.Parse (vals [idx]) > 31) {
-				isValidDate = false;
-				day = DateTime.DaysInMonth (year, month);
-				vals [idx] = day.ToString ();
-			} else
-				day = Int32.Parse (vals [idx]);
-			string d = GetDate (month, day, year, frm);
-
-			if (!DateTime.TryParseExact (d, format, CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTime result) ||
-				!isValidDate)
-				return false;
-			Date = result;
-			return true;
+		if (!DateTime.TryParseExact (d, format, CultureInfo.CurrentCulture, DateTimeStyles.None, out var result) ||
+		!isValidDate) {
+			return false;
 		}
+		Date = result;
+		return true;
+	}
 
-		string GetDate (int month, int day, int year, string [] fm)
-		{
-			string date = " ";
-			for (int i = 0; i < fm.Length; i++) {
-				if (fm [i].Contains ("M")) {
-					date += $"{month,2:00}";
-				} else if (fm [i].Contains ("d")) {
-					date += $"{day,2:00}";
+	string GetDate (int month, int day, int year, string [] fm)
+	{
+		string date = " ";
+		for (int i = 0; i < fm.Length; i++) {
+			if (fm [i].Contains ("M")) {
+				date += $"{month,2:00}";
+			} else if (fm [i].Contains ("d")) {
+				date += $"{day,2:00}";
+			} else {
+				if (!isShort && year.ToString ().Length == 2) {
+					string y = DateTime.Now.Year.ToString ();
+					date += y.Substring (0, 2) + year.ToString ();
+				} else if (isShort && year.ToString ().Length == 4) {
+					date += $"{year.ToString ().Substring (2, 2)}";
 				} else {
-					if (!isShort && year.ToString ().Length == 2) {
-						var y = DateTime.Now.Year.ToString ();
-						date += y.Substring (0, 2) + year.ToString ();
-					} else if (isShort && year.ToString ().Length == 4) {
-						date += $"{year.ToString ().Substring (2, 2)}";
-					} else {
-						date += $"{year,2:00}";
-					}
+					date += $"{year,2:00}";
 				}
-				if (i < 2)
-					date += $"{sepChar}";
 			}
-			return date;
+			if (i < 2) {
+				date += $"{sepChar}";
+			}
 		}
+		return date;
+	}
 
-		string GetDate (string text)
-		{
-			string [] vals = text.Split (sepChar);
-			string [] frm = format.Split (sepChar);
-			string [] date = { null, null, null };
-
-			for (int i = 0; i < frm.Length; i++) {
-				if (frm [i].Contains ("M")) {
-					date [0] = vals [i].Trim ();
-				} else if (frm [i].Contains ("d")) {
-					date [1] = vals [i].Trim ();
+	string GetDate (string text)
+	{
+		string [] vals = text.Split (sepChar);
+		string [] frm = format.Split (sepChar);
+		string [] date = { null, null, null };
+
+		for (int i = 0; i < frm.Length; i++) {
+			if (frm [i].Contains ("M")) {
+				date [0] = vals [i].Trim ();
+			} else if (frm [i].Contains ("d")) {
+				date [1] = vals [i].Trim ();
+			} else {
+				string year = vals [i].Trim ();
+				if (year.GetRuneCount () == 2) {
+					string y = DateTime.Now.Year.ToString ();
+					date [2] = y.Substring (0, 2) + year.ToString ();
 				} else {
-					var year = vals [i].Trim ();
-					if (year.GetRuneCount () == 2) {
-						var y = DateTime.Now.Year.ToString ();
-						date [2] = y.Substring (0, 2) + year.ToString ();
-					} else {
-						date [2] = vals [i].Trim ();
-					}
+					date [2] = vals [i].Trim ();
 				}
 			}
-			return date [0] + sepChar + date [1] + sepChar + date [2];
 		}
+		return date [0] + sepChar + date [1] + sepChar + date [2];
+	}
 
-		int GetFormatIndex (string [] fm, string t)
-		{
-			int idx = -1;
-			for (int i = 0; i < fm.Length; i++) {
-				if (fm [i].Contains (t)) {
-					idx = i;
-					break;
-				}
+	int GetFormatIndex (string [] fm, string t)
+	{
+		int idx = -1;
+		for (int i = 0; i < fm.Length; i++) {
+			if (fm [i].Contains (t)) {
+				idx = i;
+				break;
 			}
-			return idx;
 		}
+		return idx;
+	}
 
-		void IncCursorPosition ()
-		{
-			if (CursorPosition == fieldLen)
-				return;
-			if (Text [++CursorPosition] == sepChar.ToCharArray () [0])
-				CursorPosition++;
+	void IncCursorPosition ()
+	{
+		if (CursorPosition == fieldLen) {
+			return;
 		}
-
-		void DecCursorPosition ()
-		{
-			if (CursorPosition == 1)
-				return;
-			if (Text [--CursorPosition] == sepChar.ToCharArray () [0])
-				CursorPosition--;
+		if (Text [++CursorPosition] == sepChar.ToCharArray () [0]) {
+			CursorPosition++;
 		}
+	}
 
-		void AdjCursorPosition ()
-		{
-			if (Text [CursorPosition] == sepChar.ToCharArray () [0])
-				CursorPosition++;
+	void DecCursorPosition ()
+	{
+		if (CursorPosition == 1) {
+			return;
 		}
-
-		/// <inheritdoc/>
-		public override bool ProcessKey (KeyEvent kb)
-		{
-			var result = InvokeKeybindings (kb);
-			if (result != null) {
-				return (bool)result;
-			}
-			// Ignore non-numeric characters.
-			if (kb.Key < (Key)((int)'0') || kb.Key > (Key)((int)'9')) {
-				return false;
-			}
-
-			if (ReadOnly) {
-				return true;
-			}
-
-			// BUGBUG: This is a hack, we should be able to just use ((Rune)(uint)kb.Key) directly.
-			if (SetText (((Rune)(uint)kb.Key).ToString ().EnumerateRunes ().First ())) {
-				IncCursorPosition ();
-			}
-
-			return true;
+		if (Text [--CursorPosition] == sepChar.ToCharArray () [0]) {
+			CursorPosition--;
 		}
+	}
 
-		bool MoveRight ()
-		{
-			IncCursorPosition ();
-			return true;
+	void AdjCursorPosition ()
+	{
+		if (Text [CursorPosition] == sepChar.ToCharArray () [0]) {
+			CursorPosition++;
 		}
+	}
 
-		new bool MoveEnd ()
-		{
-			CursorPosition = fieldLen;
-			return true;
-		}
+	bool MoveRight ()
+	{
+		IncCursorPosition ();
+		return true;
+	}
 
-		bool MoveLeft ()
-		{
-			DecCursorPosition ();
-			return true;
-		}
+	new bool MoveEnd ()
+	{
+		CursorPosition = fieldLen;
+		return true;
+	}
 
-		bool MoveHome ()
-		{
-			// Home, C-A
-			CursorPosition = 1;
-			return true;
-		}
+	bool MoveLeft ()
+	{
+		DecCursorPosition ();
+		return true;
+	}
 
-		/// <inheritdoc/>
-		public override void DeleteCharLeft (bool useOldCursorPos = true)
-		{
-			if (ReadOnly) {
-				return;
-			}
+	bool MoveHome ()
+	{
+		// Home, C-A
+		CursorPosition = 1;
+		return true;
+	}
 
-			SetText ((Rune)'0');
-			DecCursorPosition ();
+	/// <inheritdoc/>
+	public override void DeleteCharLeft (bool useOldCursorPos = true)
+	{
+		if (ReadOnly) {
 			return;
 		}
 
-		/// <inheritdoc/>
-		public override void DeleteCharRight ()
-		{
-			if (ReadOnly)
-				return;
+		SetText ((Rune)'0');
+		DecCursorPosition ();
+		return;
+	}
 
-			SetText ((Rune)'0');
+	/// <inheritdoc/>
+	public override void DeleteCharRight ()
+	{
+		if (ReadOnly) {
 			return;
 		}
 
-		/// <inheritdoc/>
-		public override bool MouseEvent (MouseEvent ev)
-		{
-			if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked))
-				return false;
-			if (!HasFocus)
-				SetFocus ();
-
-			var point = ev.X;
-			if (point > fieldLen)
-				point = fieldLen;
-			if (point < 1)
-				point = 1;
-			CursorPosition = point;
-			AdjCursorPosition ();
-			return true;
+		SetText ((Rune)'0');
+		return;
+	}
+
+	/// <inheritdoc/>
+	public override bool MouseEvent (MouseEvent ev)
+	{
+		if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked)) {
+			return false;
+		}
+		if (!HasFocus) {
+			SetFocus ();
 		}
 
-		/// <summary>
-		/// Event firing method for the <see cref="DateChanged"/> event.
-		/// </summary>
-		/// <param name="args">Event arguments</param>
-		public virtual void OnDateChanged (DateTimeEventArgs<DateTime> args)
-		{
-			DateChanged?.Invoke (this, args);
+		int point = ev.X;
+		if (point > fieldLen) {
+			point = fieldLen;
+		}
+		if (point < 1) {
+			point = 1;
 		}
+		CursorPosition = point;
+		AdjCursorPosition ();
+		return true;
 	}
+
+	/// <summary>
+	/// Event firing method for the <see cref="DateChanged"/> event.
+	/// </summary>
+	/// <param name="args">Event arguments</param>
+	public virtual void OnDateChanged (DateTimeEventArgs<DateTime> args) => DateChanged?.Invoke (this, args);
 }

+ 5 - 4
Terminal.Gui/Views/Dialog.cs

@@ -228,15 +228,16 @@ namespace Terminal.Gui {
 			}
 		}
 
+		// BUGBUG: Why is this not handled by a key binding???
 		///<inheritdoc/>
-		public override bool ProcessKey (KeyEvent kb)
+		public override bool OnProcessKeyDown (Key a)
 		{
-			switch (kb.Key) {
-			case Key.Esc:
+			switch (a.KeyCode) {
+			case KeyCode.Esc:
 				Application.RequestStop (this);
 				return true;
 			}
-			return base.ProcessKey (kb);
+			return false;
 		}
 	}
 }

+ 61 - 66
Terminal.Gui/Views/FileDialog.cs

@@ -142,22 +142,23 @@ namespace Terminal.Gui {
 
 			this.btnOk = new Button (Style.OkButtonText) {
 				Y = Pos.AnchorEnd (1),
-				X = Pos.Function (CalculateOkButtonPosX)
+				X = Pos.Function (CalculateOkButtonPosX),
+				IsDefault = true
 			};
 			this.btnOk.Clicked += (s, e) => this.Accept (true);
-			this.btnOk.KeyPressed += (s, k) => {
-				this.NavigateIf (k, Key.CursorLeft, this.btnCancel);
-				this.NavigateIf (k, Key.CursorUp, this.tableView);
+			this.btnOk.KeyDown += (s, k) => {
+				this.NavigateIf (k, KeyCode.CursorLeft, this.btnCancel);
+				this.NavigateIf (k, KeyCode.CursorUp, this.tableView);
 			};
 
 			this.btnCancel = new Button (Strings.btnCancel) {
 				Y = Pos.AnchorEnd (1),
 				X = Pos.Right (btnOk) + 1
 			};
-			this.btnCancel.KeyPressed += (s, k) => {
-				this.NavigateIf (k, Key.CursorLeft, this.btnToggleSplitterCollapse);
-				this.NavigateIf (k, Key.CursorUp, this.tableView);
-				this.NavigateIf (k, Key.CursorRight, this.btnOk);
+			this.btnCancel.KeyDown += (s, k) => {
+				this.NavigateIf (k, KeyCode.CursorLeft, this.btnToggleSplitterCollapse);
+				this.NavigateIf (k, KeyCode.CursorUp, this.tableView);
+				this.NavigateIf (k, KeyCode.CursorRight, this.btnOk);
 			};
 			this.btnCancel.Clicked += (s, e) => {
 				Application.RequestStop ();
@@ -179,11 +180,11 @@ namespace Terminal.Gui {
 				Width = Dim.Fill (0),
 				CaptionColor = new Color (Color.Black)
 			};
-			this.tbPath.KeyPressed += (s, k) => {
+			this.tbPath.KeyDown += (s, k) => {
 
 				ClearFeedback ();
 
-				this.AcceptIf (k, Key.Enter);
+				this.AcceptIf (k, KeyCode.Enter);
 
 				this.SuppressIfBadChar (k);
 			};
@@ -207,7 +208,7 @@ namespace Terminal.Gui {
 				FullRowSelect = true,
 				CollectionNavigator = new FileDialogCollectionNavigator (this)
 			};
-			this.tableView.AddKeyBinding (Key.Space, Command.ToggleChecked);
+			this.tableView.KeyBindings.Add (KeyCode.Space, Command.ToggleChecked);
 			this.tableView.MouseClick += OnTableViewMouseClick;
 			tableView.Style.InvertSelectedCellFirstCharacter = true;
 			Style.TableStyle = tableView.Style;
@@ -228,16 +229,16 @@ namespace Terminal.Gui {
 			typeStyle.MinWidth = 6;
 			typeStyle.ColorGetter = this.ColorGetter;
 
-			this.tableView.KeyPressed += (s, k) => {
+			this.tableView.KeyDown += (s, k) => {
 				if (this.tableView.SelectedRow <= 0) {
-					this.NavigateIf (k, Key.CursorUp, this.tbPath);
+					this.NavigateIf (k, KeyCode.CursorUp, this.tbPath);
 				}
 				if (this.tableView.SelectedRow == this.tableView.Table.Rows - 1) {
-					this.NavigateIf (k, Key.CursorDown, this.btnToggleSplitterCollapse);
+					this.NavigateIf (k, KeyCode.CursorDown, this.btnToggleSplitterCollapse);
 				}
 
 				if (splitContainer.Tiles.First ().ContentView.Visible && tableView.SelectedColumn == 0) {
-					this.NavigateIf (k, Key.CursorLeft, this.treeView);
+					this.NavigateIf (k, KeyCode.CursorLeft, this.treeView);
 				}
 
 				if (k.Handled) {
@@ -277,6 +278,7 @@ namespace Terminal.Gui {
 				CaptionColor = new Color (Color.Black),
 				Width = 30,
 				Y = Pos.AnchorEnd (1),
+				HotKey = KeyCode.F | KeyCode.AltMask
 			};
 			spinnerView = new SpinnerView () {
 				X = Pos.Right (tbFind) + 1,
@@ -285,22 +287,22 @@ namespace Terminal.Gui {
 			};
 
 			tbFind.TextChanged += (s, o) => RestartSearch ();
-			tbFind.KeyPressed += (s, o) => {
-				if (o.KeyEvent.Key == Key.Enter) {
+			tbFind.KeyDown += (s, o) => {
+				if (o.KeyCode == KeyCode.Enter) {
 					RestartSearch ();
 					o.Handled = true;
 				}
 
-				if (o.KeyEvent.Key == Key.Esc) {
+				if (o.KeyCode == KeyCode.Esc) {
 					if (CancelSearch ()) {
 						o.Handled = true;
 					}
 				}
 				if (tbFind.CursorIsAtEnd ()) {
-					NavigateIf (o, Key.CursorRight, btnCancel);
+					NavigateIf (o, KeyCode.CursorRight, btnCancel);
 				}
 				if (tbFind.CursorIsAtStart ()) {
-					NavigateIf (o, Key.CursorLeft, btnToggleSplitterCollapse);
+					NavigateIf (o, KeyCode.CursorLeft, btnToggleSplitterCollapse);
 				}
 			};
 
@@ -316,23 +318,23 @@ namespace Terminal.Gui {
 			this.tbPath.TextChanged += (s, e) => this.PathChanged ();
 
 			this.tableView.CellActivated += this.CellActivate;
-			this.tableView.KeyUp += (s, k) => k.Handled = this.TableView_KeyUp (k.KeyEvent);
+			this.tableView.KeyUp += (s, k) => k.Handled = this.TableView_KeyUp (k);
 			this.tableView.SelectedCellChanged += this.TableView_SelectedCellChanged;
 
-			this.tableView.AddKeyBinding (Key.Home, Command.TopHome);
-			this.tableView.AddKeyBinding (Key.End, Command.BottomEnd);
-			this.tableView.AddKeyBinding (Key.Home | Key.ShiftMask, Command.TopHomeExtend);
-			this.tableView.AddKeyBinding (Key.End | Key.ShiftMask, Command.BottomEndExtend);
+			this.tableView.KeyBindings.Add (KeyCode.Home, Command.TopHome);
+			this.tableView.KeyBindings.Add (KeyCode.End, Command.BottomEnd);
+			this.tableView.KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.TopHomeExtend);
+			this.tableView.KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.BottomEndExtend);
 
 			this.treeView.KeyDown += (s, k) => {
 
 				var selected = treeView.SelectedObject;
 				if (selected != null) {
 					if (!treeView.CanExpand (selected) || treeView.IsExpanded (selected)) {
-						this.NavigateIf (k, Key.CursorRight, this.tableView);
+						this.NavigateIf (k, KeyCode.CursorRight, this.tableView);
 					} else
 					if (treeView.GetObjectRow (selected) == 0) {
-						this.NavigateIf (k, Key.CursorUp, this.tbPath);
+						this.NavigateIf (k, KeyCode.CursorUp, this.tbPath);
 					}
 				}
 
@@ -340,7 +342,7 @@ namespace Terminal.Gui {
 					return;
 				}
 
-				k.Handled = this.TreeView_KeyDown (k.KeyEvent);
+				k.Handled = this.TreeView_KeyDown (k);
 
 			};
 
@@ -484,23 +486,26 @@ namespace Terminal.Gui {
 		}
 
 
-		/// <inheritdoc/>
-		public override bool ProcessHotKey (KeyEvent keyEvent)
-		{
-			if (this.NavigateIf (keyEvent, Key.CtrlMask | Key.F, this.tbFind)) {
-				return true;
-			}
+//		/// <inheritdoc/>
+//		public override bool OnHotKey (KeyEventArgs keyEvent)
+//		{
+//#if BROKE_IN_2927
+//			// BUGBUG: Ctrl-F is forward in a TextField. 
+//			if (this.NavigateIf (keyEvent, Key.Alt | Key.F, this.tbFind)) {
+//				return true;
+//			}
+//#endif
 
-			ClearFeedback ();
+//			ClearFeedback ();
 
-			if (allowedTypeMenuBar != null &&
-				keyEvent.Key == Key.Tab &&
-				allowedTypeMenuBar.IsMenuOpen) {
-				allowedTypeMenuBar.CloseMenu (false, false, false);
-			}
+//			if (allowedTypeMenuBar != null &&
+//				keyEvent.ConsoleDriverKey == Key.Tab &&
+//				allowedTypeMenuBar.IsMenuOpen) {
+//				allowedTypeMenuBar.CloseMenu (false, false, false);
+//			}
 
-			return base.ProcessHotKey (keyEvent);
-		}
+//			return base.OnHotKey (keyEvent);
+//		}
 		private void RestartSearch ()
 		{
 			if (disposed || State?.Directory == null) {
@@ -772,19 +777,19 @@ namespace Terminal.Gui {
 			}
 		}
 
-		private void SuppressIfBadChar (KeyEventEventArgs k)
+		private void SuppressIfBadChar (Key k)
 		{
 			// don't let user type bad letters
-			var ch = (char)k.KeyEvent.KeyValue;
+			var ch = (char)k;
 
 			if (badChars.Contains (ch)) {
 				k.Handled = true;
 			}
 		}
 
-		private bool TreeView_KeyDown (KeyEvent keyEvent)
+		private bool TreeView_KeyDown (Key keyEvent)
 		{
-			if (this.treeView.HasFocus && Separators.Contains ((char)keyEvent.KeyValue)) {
+			if (this.treeView.HasFocus && Separators.Contains ((char)keyEvent)) {
 				this.tbPath.FocusFirst ();
 
 				// let that keystroke go through on the tbPath instead
@@ -794,9 +799,9 @@ namespace Terminal.Gui {
 			return false;
 		}
 
-		private void AcceptIf (KeyEventEventArgs keyEvent, Key isKey)
+		private void AcceptIf (Key keyEvent, KeyCode isKey)
 		{
-			if (!keyEvent.Handled && keyEvent.KeyEvent.Key == isKey) {
+			if (!keyEvent.Handled && keyEvent.KeyCode == isKey) {
 				keyEvent.Handled = true;
 
 				// User hit Enter in text box so probably wants the
@@ -880,19 +885,9 @@ namespace Terminal.Gui {
 			Application.RequestStop ();
 		}
 
-		private void NavigateIf (KeyEventEventArgs keyEvent, Key isKey, View to)
-		{
-			if (!keyEvent.Handled) {
-
-				if (NavigateIf (keyEvent.KeyEvent, isKey, to)) {
-					keyEvent.Handled = true;
-				}
-			}
-		}
-
-		private bool NavigateIf (KeyEvent keyEvent, Key isKey, View to)
+		private bool NavigateIf (Key keyEvent, KeyCode isKey, View to)
 		{
-			if (keyEvent.Key == isKey) {
+			if (keyEvent.KeyCode == isKey) {
 
 				to.FocusFirst ();
 				if (to == tbPath) {
@@ -956,28 +951,28 @@ namespace Terminal.Gui {
 			}
 		}
 
-		private bool TableView_KeyUp (KeyEvent keyEvent)
+		private bool TableView_KeyUp (Key keyEvent)
 		{
-			if (keyEvent.Key == Key.Backspace) {
+			if (keyEvent.KeyCode == KeyCode.Backspace) {
 				return this.history.Back ();
 			}
-			if (keyEvent.Key == (Key.ShiftMask | Key.Backspace)) {
+			if (keyEvent.KeyCode == (KeyCode.ShiftMask | KeyCode.Backspace)) {
 				return this.history.Forward ();
 			}
 
-			if (keyEvent.Key == Key.DeleteChar) {
+			if (keyEvent.KeyCode == KeyCode.DeleteChar) {
 
 				Delete ();
 				return true;
 			}
 
-			if (keyEvent.Key == (Key.CtrlMask | Key.R)) {
+			if (keyEvent.KeyCode == (KeyCode.CtrlMask | KeyCode.R)) {
 
 				Rename ();
 				return true;
 			}
 
-			if (keyEvent.Key == (Key.CtrlMask | Key.N)) {
+			if (keyEvent.KeyCode == (KeyCode.CtrlMask | KeyCode.N)) {
 				New ();
 				return true;
 			}

+ 1 - 1
Terminal.Gui/Views/GraphView/Annotations.cs

@@ -17,7 +17,7 @@ namespace Terminal.Gui {
 	public interface IAnnotation {
 		/// <summary>
 		/// True if annotation should be drawn before <see cref="ISeries"/>.  This
-		/// allowes Series and later annotations to potentially draw over the top
+		/// allows Series and later annotations to potentially draw over the top
 		/// of this annotation.
 		/// </summary>
 		bool BeforeSeries { get; }

+ 6 - 18
Terminal.Gui/Views/GraphView/GraphView.cs

@@ -81,14 +81,14 @@ namespace Terminal.Gui {
 			AddCommand (Command.PageUp, () => { PageUp (); return true; });
 			AddCommand (Command.PageDown, () => { PageDown (); return true; });
 
-			AddKeyBinding (Key.CursorRight, Command.ScrollRight);
-			AddKeyBinding (Key.CursorLeft, Command.ScrollLeft);
-			AddKeyBinding (Key.CursorUp, Command.ScrollUp);
-			AddKeyBinding (Key.CursorDown, Command.ScrollDown);
+			KeyBindings.Add (KeyCode.CursorRight, Command.ScrollRight);
+			KeyBindings.Add (KeyCode.CursorLeft, Command.ScrollLeft);
+			KeyBindings.Add (KeyCode.CursorUp, Command.ScrollUp);
+			KeyBindings.Add (KeyCode.CursorDown, Command.ScrollDown);
 
 			// Not bound by default (preserves backwards compatibility)
-			//AddKeyBinding (Key.PageUp, Command.PageUp);
-			//AddKeyBinding (Key.PageDown, Command.PageDown);
+			//KeyBindings.Add (Key.PageUp, Command.PageUp);
+			//KeyBindings.Add (Key.PageDown, Command.PageDown);
 		}
 
 		/// <summary>
@@ -243,18 +243,6 @@ namespace Terminal.Gui {
 			return base.OnEnter (view);
 		}
 
-		/// <inheritdoc/>
-		public override bool ProcessKey (KeyEvent keyEvent)
-		{
-			if (HasFocus && CanFocus) {
-				var result = InvokeKeybindings (keyEvent);
-				if (result != null)
-					return (bool)result;
-			}
-
-			return base.ProcessKey (keyEvent);
-		}
-
 		/// <summary>
 		/// Scrolls the graph up 1 page
 		/// </summary>

+ 559 - 531
Terminal.Gui/Views/HexView.cs

@@ -10,627 +10,655 @@ using System.Collections.Generic;
 using System.IO;
 using System.Text;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui;
+
+/// <summary>
+/// An hex viewer and editor <see cref="View"/> over a <see cref="System.IO.Stream"/>
+/// </summary>
+/// <remarks>
+/// <para>
+/// <see cref="HexView"/> provides a hex editor on top of a seekable <see cref="Stream"/> with the left side showing an hex
+/// dump of the values in the <see cref="Stream"/> and the right side showing the contents (filtered to 
+/// non-control sequence ASCII characters).    
+/// </para>
+/// <para>
+/// Users can switch from one side to the other by using the tab key.  
+/// </para>
+/// <para>
+/// To enable editing, set <see cref="AllowEdits"/> to true. When <see cref="AllowEdits"/> is true 
+/// the user can make changes to the hexadecimal values of the <see cref="Stream"/>. Any changes are tracked
+/// in the <see cref="Edits"/> property (a <see cref="SortedDictionary{TKey, TValue}"/>) indicating 
+/// the position where the changes were made and the new values. A convenience method, <see cref="ApplyEdits"/>
+/// will apply the edits to the <see cref="Stream"/>.
+/// </para>
+/// <para>
+/// Control the first byte shown by setting the <see cref="DisplayStart"/> property 
+/// to an offset in the stream.
+/// </para>
+/// </remarks>
+public partial class HexView : View {
+	SortedDictionary<long, byte> edits = new SortedDictionary<long, byte> ();
+	Stream source;
+	long displayStart, pos;
+	bool firstNibble, leftSide;
+
+	long position {
+		get => pos;
+		set {
+			pos = value;
+			OnPositionChanged ();
+		}
+	}
+
+	/// <summary>
+	/// Initializes a <see cref="HexView"/> class using <see cref="LayoutStyle.Computed"/> layout.
+	/// </summary>
+	/// <param name="source">The <see cref="Stream"/> to view and edit as hex, this <see cref="Stream"/> must support seeking, or an exception will be thrown.</param>
+	public HexView (Stream source) : base ()
+	{
+		Source = source;
+		CanFocus = true;
+		leftSide = true;
+		firstNibble = true;
+
+		// Things this view knows how to do
+		AddCommand (Command.Left, () => MoveLeft ());
+		AddCommand (Command.Right, () => MoveRight ());
+		AddCommand (Command.LineDown, () => MoveDown (bytesPerLine));
+		AddCommand (Command.LineUp, () => MoveUp (bytesPerLine));
+		AddCommand (Command.ToggleChecked, () => ToggleSide ());
+		AddCommand (Command.PageUp, () => MoveUp (bytesPerLine * Frame.Height));
+		AddCommand (Command.PageDown, () => MoveDown (bytesPerLine * Frame.Height));
+		AddCommand (Command.TopHome, () => MoveHome ());
+		AddCommand (Command.BottomEnd, () => MoveEnd ());
+		AddCommand (Command.StartOfLine, () => MoveStartOfLine ());
+		AddCommand (Command.EndOfLine, () => MoveEndOfLine ());
+		AddCommand (Command.StartOfPage, () => MoveUp (bytesPerLine * ((int)(position - displayStart) / bytesPerLine)));
+		AddCommand (Command.EndOfPage, () => MoveDown (bytesPerLine * (Frame.Height - 1 - (int)(position - displayStart) / bytesPerLine)));
+
+		// Default keybindings for this view
+		KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
+		KeyBindings.Add (KeyCode.CursorRight, Command.Right);
+		KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
+		KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
+		KeyBindings.Add (KeyCode.Enter, Command.ToggleChecked);
+
+		KeyBindings.Add ('v' + KeyCode.AltMask, Command.PageUp);
+		KeyBindings.Add (KeyCode.PageUp, Command.PageUp);
+
+		KeyBindings.Add (KeyCode.V | KeyCode.CtrlMask, Command.PageDown);
+		KeyBindings.Add (KeyCode.PageDown, Command.PageDown);
+
+		KeyBindings.Add (KeyCode.Home, Command.TopHome);
+		KeyBindings.Add (KeyCode.End, Command.BottomEnd);
+		KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask, Command.StartOfLine);
+		KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask, Command.EndOfLine);
+		KeyBindings.Add (KeyCode.CursorUp | KeyCode.CtrlMask, Command.StartOfPage);
+		KeyBindings.Add (KeyCode.CursorDown | KeyCode.CtrlMask, Command.EndOfPage);
+	}
+
 	/// <summary>
-	/// An hex viewer and editor <see cref="View"/> over a <see cref="System.IO.Stream"/>
+	/// Initializes a <see cref="HexView"/> class using <see cref="LayoutStyle.Computed"/> layout.
 	/// </summary>
-	/// <remarks>
-	/// <para>
-	/// <see cref="HexView"/> provides a hex editor on top of a seekable <see cref="Stream"/> with the left side showing an hex
-	/// dump of the values in the <see cref="Stream"/> and the right side showing the contents (filtered to 
-	/// non-control sequence ASCII characters).    
-	/// </para>
-	/// <para>
-	/// Users can switch from one side to the other by using the tab key.  
-	/// </para>
-	/// <para>
-	/// To enable editing, set <see cref="AllowEdits"/> to true. When <see cref="AllowEdits"/> is true 
-	/// the user can make changes to the hexadecimal values of the <see cref="Stream"/>. Any changes are tracked
-	/// in the <see cref="Edits"/> property (a <see cref="SortedDictionary{TKey, TValue}"/>) indicating 
-	/// the position where the changes were made and the new values. A convenience method, <see cref="ApplyEdits"/>
-	/// will apply the edits to the <see cref="Stream"/>.
-	/// </para>
-	/// <para>
-	/// Control the first byte shown by setting the <see cref="DisplayStart"/> property 
-	/// to an offset in the stream.
-	/// </para>
-	/// </remarks>
-	public partial class HexView : View {
-		SortedDictionary<long, byte> edits = new SortedDictionary<long, byte> ();
-		Stream source;
-		long displayStart, pos;
-		bool firstNibble, leftSide;
-
-		private long position {
-			get => pos;
-			set {
-				pos = value;
-				OnPositionChanged ();
+	public HexView () : this (source: new MemoryStream ()) { }
+
+	/// <summary>
+	/// Event to be invoked when an edit is made on the <see cref="Stream"/>.
+	/// </summary>
+	public event EventHandler<HexViewEditEventArgs> Edited;
+
+	/// <summary>
+	/// Event to be invoked when the position and cursor position changes.
+	/// </summary>
+	public event EventHandler<HexViewEventArgs> PositionChanged;
+
+	/// <summary>
+	/// Sets or gets the <see cref="Stream"/> the <see cref="HexView"/> is operating on; the stream must support seeking (<see cref="Stream.CanSeek"/> == true).
+	/// </summary>
+	/// <value>The source.</value>
+	public Stream Source {
+		get => source;
+		set {
+			if (value == null) {
+				throw new ArgumentNullException ("source");
 			}
-		}
+			if (!value.CanSeek) {
+				throw new ArgumentException ("The source stream must be seekable (CanSeek property)", "source");
+			}
+			source = value;
 
-		/// <summary>
-		/// Initializes a <see cref="HexView"/> class using <see cref="LayoutStyle.Computed"/> layout.
-		/// </summary>
-		/// <param name="source">The <see cref="Stream"/> to view and edit as hex, this <see cref="Stream"/> must support seeking, or an exception will be thrown.</param>
-		public HexView (Stream source) : base ()
-		{
-			Source = source;
-			CanFocus = true;
-			leftSide = true;
-			firstNibble = true;
-
-			// Things this view knows how to do
-			AddCommand (Command.Left, () => MoveLeft ());
-			AddCommand (Command.Right, () => MoveRight ());
-			AddCommand (Command.LineDown, () => MoveDown (bytesPerLine));
-			AddCommand (Command.LineUp, () => MoveUp (bytesPerLine));
-			AddCommand (Command.ToggleChecked, () => ToggleSide ());
-			AddCommand (Command.PageUp, () => MoveUp (bytesPerLine * Frame.Height));
-			AddCommand (Command.PageDown, () => MoveDown (bytesPerLine * Frame.Height));
-			AddCommand (Command.TopHome, () => MoveHome ());
-			AddCommand (Command.BottomEnd, () => MoveEnd ());
-			AddCommand (Command.StartOfLine, () => MoveStartOfLine ());
-			AddCommand (Command.EndOfLine, () => MoveEndOfLine ());
-			AddCommand (Command.StartOfPage, () => MoveUp (bytesPerLine * ((int)(position - displayStart) / bytesPerLine)));
-			AddCommand (Command.EndOfPage, () => MoveDown (bytesPerLine * (Frame.Height - 1 - ((int)(position - displayStart) / bytesPerLine))));
-
-			// Default keybindings for this view
-			AddKeyBinding (Key.CursorLeft, Command.Left);
-			AddKeyBinding (Key.CursorRight, Command.Right);
-			AddKeyBinding (Key.CursorDown, Command.LineDown);
-			AddKeyBinding (Key.CursorUp, Command.LineUp);
-			AddKeyBinding (Key.Enter, Command.ToggleChecked);
-
-			AddKeyBinding ('v' + Key.AltMask, Command.PageUp);
-			AddKeyBinding (Key.PageUp, Command.PageUp);
-
-			AddKeyBinding (Key.V | Key.CtrlMask, Command.PageDown);
-			AddKeyBinding (Key.PageDown, Command.PageDown);
-
-			AddKeyBinding (Key.Home, Command.TopHome);
-			AddKeyBinding (Key.End, Command.BottomEnd);
-			AddKeyBinding (Key.CursorLeft | Key.CtrlMask, Command.StartOfLine);
-			AddKeyBinding (Key.CursorRight | Key.CtrlMask, Command.EndOfLine);
-			AddKeyBinding (Key.CursorUp | Key.CtrlMask, Command.StartOfPage);
-			AddKeyBinding (Key.CursorDown | Key.CtrlMask, Command.EndOfPage);
-		}
-
-		/// <summary>
-		/// Initializes a <see cref="HexView"/> class using <see cref="LayoutStyle.Computed"/> layout.
-		/// </summary>
-		public HexView () : this (source: new MemoryStream ()) { }
-
-		/// <summary>
-		/// Event to be invoked when an edit is made on the <see cref="Stream"/>.
-		/// </summary>
-		public event EventHandler<HexViewEditEventArgs> Edited;
-
-		/// <summary>
-		/// Event to be invoked when the position and cursor position changes.
-		/// </summary>
-		public event EventHandler<HexViewEventArgs> PositionChanged;
-
-		/// <summary>
-		/// Sets or gets the <see cref="Stream"/> the <see cref="HexView"/> is operating on; the stream must support seeking (<see cref="Stream.CanSeek"/> == true).
-		/// </summary>
-		/// <value>The source.</value>
-		public Stream Source {
-			get => source;
-			set {
-				if (value == null)
-					throw new ArgumentNullException ("source");
-				if (!value.CanSeek)
-					throw new ArgumentException ("The source stream must be seekable (CanSeek property)", "source");
-				source = value;
-
-				if (displayStart > source.Length)
-					DisplayStart = 0;
-				if (position > source.Length)
-					position = 0;
-				SetNeedsDisplay ();
+			if (displayStart > source.Length) {
+				DisplayStart = 0;
+			}
+			if (position > source.Length) {
+				position = 0;
 			}
+			SetNeedsDisplay ();
 		}
+	}
 
-		internal void SetDisplayStart (long value)
-		{
-			if (value > 0 && value >= source.Length)
-				displayStart = source.Length - 1;
-			else if (value < 0)
-				displayStart = 0;
-			else
-				displayStart = value;
-			SetNeedsDisplay ();
+	internal void SetDisplayStart (long value)
+	{
+		if (value > 0 && value >= source.Length) {
+			displayStart = source.Length - 1;
+		} else if (value < 0) {
+			displayStart = 0;
+		} else {
+			displayStart = value;
 		}
+		SetNeedsDisplay ();
+	}
 
-		/// <summary>
-		/// Sets or gets the offset into the <see cref="Stream"/> that will displayed at the top of the <see cref="HexView"/>
-		/// </summary>
-		/// <value>The display start.</value>
-		public long DisplayStart {
-			get => displayStart;
-			set {
-				position = value;
+	/// <summary>
+	/// Sets or gets the offset into the <see cref="Stream"/> that will displayed at the top of the <see cref="HexView"/>
+	/// </summary>
+	/// <value>The display start.</value>
+	public long DisplayStart {
+		get => displayStart;
+		set {
+			position = value;
 
-				SetDisplayStart (value);
-			}
+			SetDisplayStart (value);
 		}
+	}
 
-		const int displayWidth = 9;
-		const int bsize = 4;
-		int bpl;
-		private int bytesPerLine {
-			get => bpl;
-			set {
-				bpl = value;
-				OnPositionChanged ();
-			}
+	const int displayWidth = 9;
+	const int bsize = 4;
+	int bpl;
+
+	int bytesPerLine {
+		get => bpl;
+		set {
+			bpl = value;
+			OnPositionChanged ();
 		}
+	}
 
-		/// <inheritdoc/>
-		public override Rect Frame {
-			get => base.Frame;
-			set {
-				base.Frame = value;
+	/// <inheritdoc/>
+	public override Rect Frame {
+		get => base.Frame;
+		set {
+			base.Frame = value;
 
-				// Small buffers will just show the position, with the bsize field value (4 bytes)
-				bytesPerLine = bsize;
-				if (value.Width - displayWidth > 17)
-					bytesPerLine = bsize * ((value.Width - displayWidth) / 18);
+			// Small buffers will just show the position, with the bsize field value (4 bytes)
+			bytesPerLine = bsize;
+			if (value.Width - displayWidth > 17) {
+				bytesPerLine = bsize * ((value.Width - displayWidth) / 18);
 			}
 		}
+	}
 
-		//
-		// This is used to support editing of the buffer on a peer List<>, 
-		// the offset corresponds to an offset relative to DisplayStart, and
-		// the buffer contains the contents of a screenful of data, so the 
-		// offset is relative to the buffer.
-		//
-		// 
-		byte GetData (byte [] buffer, int offset, out bool edited)
-		{
-			var pos = DisplayStart + offset;
-			if (edits.TryGetValue (pos, out byte v)) {
-				edited = true;
-				return v;
-			}
-			edited = false;
-			return buffer [offset];
+	//
+	// This is used to support editing of the buffer on a peer List<>, 
+	// the offset corresponds to an offset relative to DisplayStart, and
+	// the buffer contains the contents of a screenful of data, so the 
+	// offset is relative to the buffer.
+	//
+	// 
+	byte GetData (byte [] buffer, int offset, out bool edited)
+	{
+		long pos = DisplayStart + offset;
+		if (edits.TryGetValue (pos, out byte v)) {
+			edited = true;
+			return v;
 		}
+		edited = false;
+		return buffer [offset];
+	}
 
-		///<inheritdoc/>
-		public override void OnDrawContent (Rect contentArea)
-		{
-			Attribute currentAttribute;
-			var current = ColorScheme.Focus;
-			Driver.SetAttribute (current);
-			Move (0, 0);
-
-			var frame = Frame;
-
-			var nblocks = bytesPerLine / bsize;
-			var data = new byte [nblocks * bsize * frame.Height];
-			Source.Position = displayStart;
-			var n = source.Read (data, 0, data.Length);
-
-			var activeColor = ColorScheme.HotNormal;
-			var trackingColor = ColorScheme.HotFocus;
-
-			for (int line = 0; line < frame.Height; line++) {
-				var lineRect = new Rect (0, line, frame.Width, 1);
-				if (!Bounds.Contains (lineRect))
-					continue;
-
-				Move (0, line);
-				Driver.SetAttribute (ColorScheme.HotNormal);
-				Driver.AddStr (string.Format ("{0:x8} ", displayStart + line * nblocks * bsize));
-
-				currentAttribute = ColorScheme.HotNormal;
-				SetAttribute (GetNormalColor ());
-
-				for (int block = 0; block < nblocks; block++) {
-					for (int b = 0; b < bsize; b++) {
-						var offset = (line * nblocks * bsize) + block * bsize + b;
-						var value = GetData (data, offset, out bool edited);
-						if (offset + displayStart == position || edited)
-							SetAttribute (leftSide ? activeColor : trackingColor);
-						else
-							SetAttribute (GetNormalColor ());
-
-						Driver.AddStr (offset >= n && !edited ? "  " : string.Format ("{0:x2}", value));
-						SetAttribute (GetNormalColor ());
-						Driver.AddRune ((Rune)' ');
-					}
-					Driver.AddStr (block + 1 == nblocks ? " " : "| ");
-				}
+	///<inheritdoc/>
+	public override void OnDrawContent (Rect contentArea)
+	{
+		Attribute currentAttribute;
+		var current = ColorScheme.Focus;
+		Driver.SetAttribute (current);
+		Move (0, 0);
 
-				for (int bitem = 0; bitem < nblocks * bsize; bitem++) {
-					var offset = line * nblocks * bsize + bitem;
-					var b = GetData (data, offset, out bool edited);
-					Rune c;
-					if (offset >= n && !edited)
-						c = (Rune)' ';
-					else {
-						if (b < 32)
-							c = (Rune)'.';
-						else if (b > 127)
-							c = (Rune)'.';
-						else
-							Rune.DecodeFromUtf8 (new ReadOnlySpan<byte> (b), out c, out _);
-					}
-					if (offset + displayStart == position || edited)
-						SetAttribute (leftSide ? trackingColor : activeColor);
-					else
+		var frame = Frame;
+
+		int nblocks = bytesPerLine / bsize;
+		byte [] data = new byte [nblocks * bsize * frame.Height];
+		Source.Position = displayStart;
+		int n = source.Read (data, 0, data.Length);
+
+		var activeColor = ColorScheme.HotNormal;
+		var trackingColor = ColorScheme.HotFocus;
+
+		for (int line = 0; line < frame.Height; line++) {
+			var lineRect = new Rect (0, line, frame.Width, 1);
+			if (!Bounds.Contains (lineRect)) {
+				continue;
+			}
+
+			Move (0, line);
+			Driver.SetAttribute (ColorScheme.HotNormal);
+			Driver.AddStr (string.Format ("{0:x8} ", displayStart + line * nblocks * bsize));
+
+			currentAttribute = ColorScheme.HotNormal;
+			SetAttribute (GetNormalColor ());
+
+			for (int block = 0; block < nblocks; block++) {
+				for (int b = 0; b < bsize; b++) {
+					int offset = line * nblocks * bsize + block * bsize + b;
+					byte value = GetData (data, offset, out bool edited);
+					if (offset + displayStart == position || edited) {
+						SetAttribute (leftSide ? activeColor : trackingColor);
+					} else {
 						SetAttribute (GetNormalColor ());
+					}
 
-					Driver.AddRune (c);
+					Driver.AddStr (offset >= n && !edited ? "  " : string.Format ("{0:x2}", value));
+					SetAttribute (GetNormalColor ());
+					Driver.AddRune ((Rune)' ');
 				}
+				Driver.AddStr (block + 1 == nblocks ? " " : "| ");
 			}
 
-			void SetAttribute (Attribute attribute)
-			{
-				if (currentAttribute != attribute) {
-					currentAttribute = attribute;
-					Driver.SetAttribute (attribute);
+			for (int bitem = 0; bitem < nblocks * bsize; bitem++) {
+				int offset = line * nblocks * bsize + bitem;
+				byte b = GetData (data, offset, out bool edited);
+				Rune c;
+				if (offset >= n && !edited) {
+					c = (Rune)' ';
+				} else {
+					if (b < 32) {
+						c = (Rune)'.';
+					} else if (b > 127) {
+						c = (Rune)'.';
+					} else {
+						Rune.DecodeFromUtf8 (new ReadOnlySpan<byte> (ref b), out c, out _);
+					}
 				}
+				if (offset + displayStart == position || edited) {
+					SetAttribute (leftSide ? trackingColor : activeColor);
+				} else {
+					SetAttribute (GetNormalColor ());
+				}
+
+				Driver.AddRune (c);
 			}
 		}
 
-		///<inheritdoc/>
-		public override void PositionCursor ()
+		void SetAttribute (Attribute attribute)
 		{
-			var delta = (int)(position - displayStart);
-			var line = delta / bytesPerLine;
-			var item = delta % bytesPerLine;
-			var block = item / bsize;
-			var column = (item % bsize) * 3;
+			if (currentAttribute != attribute) {
+				currentAttribute = attribute;
+				Driver.SetAttribute (attribute);
+			}
+		}
+	}
 
-			if (leftSide)
-				Move (displayWidth + block * 14 + column + (firstNibble ? 0 : 1), line);
-			else
-				Move (displayWidth + (bytesPerLine / bsize) * 14 + item - 1, line);
+	///<inheritdoc/>
+	public override void PositionCursor ()
+	{
+		int delta = (int)(position - displayStart);
+		int line = delta / bytesPerLine;
+		int item = delta % bytesPerLine;
+		int block = item / bsize;
+		int column = item % bsize * 3;
+
+		if (leftSide) {
+			Move (displayWidth + block * 14 + column + (firstNibble ? 0 : 1), line);
+		} else {
+			Move (displayWidth + bytesPerLine / bsize * 14 + item - 1, line);
 		}
+	}
 
-		void RedisplayLine (long pos)
-		{
-			var delta = (int)(pos - DisplayStart);
-			var line = delta / bytesPerLine;
+	void RedisplayLine (long pos)
+	{
+		int delta = (int)(pos - DisplayStart);
+		int line = delta / bytesPerLine;
 
-			SetNeedsDisplay (new Rect (0, line, Frame.Width, 1));
-		}
+		SetNeedsDisplay (new Rect (0, line, Frame.Width, 1));
+	}
 
-		bool MoveEndOfLine ()
-		{
-			position = Math.Min ((position / bytesPerLine * bytesPerLine) + bytesPerLine - 1, source.Length);
-			SetNeedsDisplay ();
+	bool MoveEndOfLine ()
+	{
+		position = Math.Min (position / bytesPerLine * bytesPerLine + bytesPerLine - 1, source.Length);
+		SetNeedsDisplay ();
 
-			return true;
-		}
+		return true;
+	}
 
-		bool MoveStartOfLine ()
-		{
-			position = position / bytesPerLine * bytesPerLine;
-			SetNeedsDisplay ();
+	bool MoveStartOfLine ()
+	{
+		position = position / bytesPerLine * bytesPerLine;
+		SetNeedsDisplay ();
 
-			return true;
+		return true;
+	}
+
+	bool MoveEnd ()
+	{
+		position = source.Length;
+		if (position >= DisplayStart + bytesPerLine * Frame.Height) {
+			SetDisplayStart (position);
+			SetNeedsDisplay ();
+		} else {
+			RedisplayLine (position);
 		}
 
-		bool MoveEnd ()
-		{
-			position = source.Length;
-			if (position >= (DisplayStart + bytesPerLine * Frame.Height)) {
-				SetDisplayStart (position);
-				SetNeedsDisplay ();
-			} else
-				RedisplayLine (position);
+		return true;
+	}
 
-			return true;
-		}
+	bool MoveHome ()
+	{
+		DisplayStart = 0;
+		SetNeedsDisplay ();
 
-		bool MoveHome ()
-		{
-			DisplayStart = 0;
-			SetNeedsDisplay ();
+		return true;
+	}
 
-			return true;
-		}
+	bool ToggleSide ()
+	{
+		leftSide = !leftSide;
+		RedisplayLine (position);
+		firstNibble = true;
 
-		bool ToggleSide ()
-		{
-			leftSide = !leftSide;
-			RedisplayLine (position);
-			firstNibble = true;
+		return true;
+	}
 
+	bool MoveLeft ()
+	{
+		RedisplayLine (position);
+		if (leftSide) {
+			if (!firstNibble) {
+				firstNibble = true;
+				return true;
+			}
+			firstNibble = false;
+		}
+		if (position == 0) {
 			return true;
 		}
-
-		bool MoveLeft ()
-		{
+		if (position - 1 < DisplayStart) {
+			SetDisplayStart (displayStart - bytesPerLine);
+			SetNeedsDisplay ();
+		} else {
 			RedisplayLine (position);
-			if (leftSide) {
-				if (!firstNibble) {
-					firstNibble = true;
-					return true;
-				}
+		}
+		position--;
+
+		return true;
+	}
+
+	bool MoveRight ()
+	{
+		RedisplayLine (position);
+		if (leftSide) {
+			if (firstNibble) {
 				firstNibble = false;
-			}
-			if (position == 0)
 				return true;
-			if (position - 1 < DisplayStart) {
-				SetDisplayStart (displayStart - bytesPerLine);
-				SetNeedsDisplay ();
-			} else
-				RedisplayLine (position);
-			position--;
-
-			return true;
+			} else {
+				firstNibble = true;
+			}
 		}
-
-		bool MoveRight ()
-		{
+		if (position < source.Length) {
+			position++;
+		}
+		if (position >= DisplayStart + bytesPerLine * Frame.Height) {
+			SetDisplayStart (DisplayStart + bytesPerLine);
+			SetNeedsDisplay ();
+		} else {
 			RedisplayLine (position);
-			if (leftSide) {
-				if (firstNibble) {
-					firstNibble = false;
-					return true;
-				} else
-					firstNibble = true;
-			}
-			if (position < source.Length)
-				position++;
-			if (position >= (DisplayStart + bytesPerLine * Frame.Height)) {
-				SetDisplayStart (DisplayStart + bytesPerLine);
-				SetNeedsDisplay ();
-			} else
-				RedisplayLine (position);
+		}
 
-			return true;
+		return true;
+	}
+
+	bool MoveUp (int bytes)
+	{
+		RedisplayLine (position);
+		if (position - bytes > -1) {
+			position -= bytes;
+		}
+		if (position < DisplayStart) {
+			SetDisplayStart (DisplayStart - bytes);
+			SetNeedsDisplay ();
+		} else {
+			RedisplayLine (position);
 		}
 
-		bool MoveUp (int bytes)
-		{
+		return true;
+	}
+
+	bool MoveDown (int bytes)
+	{
+		RedisplayLine (position);
+		if (position + bytes < source.Length) {
+			position += bytes;
+		} else if (bytes == bytesPerLine * Frame.Height && source.Length >= DisplayStart + bytesPerLine * Frame.Height
+			|| bytes <= bytesPerLine * Frame.Height - bytesPerLine && source.Length <= DisplayStart + bytesPerLine * Frame.Height) {
+			long p = position;
+			while (p + bytesPerLine < source.Length) {
+				p += bytesPerLine;
+			}
+			position = p;
+		}
+		if (position >= DisplayStart + bytesPerLine * Frame.Height) {
+			SetDisplayStart (DisplayStart + bytes);
+			SetNeedsDisplay ();
+		} else {
 			RedisplayLine (position);
-			if (position - bytes > -1)
-				position -= bytes;
-			if (position < DisplayStart) {
-				SetDisplayStart (DisplayStart - bytes);
-				SetNeedsDisplay ();
-			} else
-				RedisplayLine (position);
+		}
 
-			return true;
+		return true;
+	}
+
+	/// <inheritdoc/>
+	public override bool OnProcessKeyDown (Key keyEvent)
+	{
+		if (!AllowEdits) {
+			return false;
 		}
 
-		bool MoveDown (int bytes)
-		{
-			RedisplayLine (position);
-			if (position + bytes < source.Length)
-				position += bytes;
-			else if ((bytes == bytesPerLine * Frame.Height && source.Length >= (DisplayStart + bytesPerLine * Frame.Height))
-				|| (bytes <= (bytesPerLine * Frame.Height - bytesPerLine) && source.Length <= (DisplayStart + bytesPerLine * Frame.Height))) {
-				var p = position;
-				while (p + bytesPerLine < source.Length) {
-					p += bytesPerLine;
-				}
-				position = p;
+		// Ignore control characters and other special keys
+		if (keyEvent.KeyCode < KeyCode.Space || keyEvent.KeyCode > KeyCode.CharMask) {
+			return false;
+		}
+
+		if (leftSide) {
+			int value;
+			char k = (char)keyEvent.KeyCode;
+			if (k >= 'A' && k <= 'F') {
+				value = k - 'A' + 10;
+			} else if (k >= 'a' && k <= 'f') {
+				value = k - 'a' + 10;
+			} else if (k >= '0' && k <= '9') {
+				value = k - '0';
+			} else {
+				return false;
 			}
-			if (position >= (DisplayStart + bytesPerLine * Frame.Height)) {
-				SetDisplayStart (DisplayStart + bytes);
-				SetNeedsDisplay ();
-			} else
-				RedisplayLine (position);
 
+			byte b;
+			if (!edits.TryGetValue (position, out b)) {
+				source.Position = position;
+				b = (byte)source.ReadByte ();
+			}
+			RedisplayLine (position);
+			if (firstNibble) {
+				firstNibble = false;
+				b = (byte)(b & 0xf | value << bsize);
+				edits [position] = b;
+				OnEdited (new HexViewEditEventArgs (position, edits [position]));
+			} else {
+				b = (byte)(b & 0xf0 | value);
+				edits [position] = b;
+				OnEdited (new HexViewEditEventArgs (position, edits [position]));
+				MoveRight ();
+			}
 			return true;
+		} else {
+			return false;
 		}
+	}
 
-		/// <inheritdoc/>
-		public override bool ProcessKey (KeyEvent keyEvent)
-		{
-			var result = InvokeKeybindings (keyEvent);
-			if (result != null)
-				return (bool)result;
-
-			if (!AllowEdits)
-				return false;
+	/// <summary>
+	/// Method used to invoke the <see cref="Edited"/> event passing the <see cref="KeyValuePair{TKey, TValue}"/>.
+	/// </summary>
+	/// <param name="e">The key value pair.</param>
+	public virtual void OnEdited (HexViewEditEventArgs e)
+	{
+		Edited?.Invoke (this, e);
+	}
 
-			// Ignore control characters and other special keys
-			if (keyEvent.Key < Key.Space || keyEvent.Key > Key.CharMask)
-				return false;
+	/// <summary>
+	/// Method used to invoke the <see cref="PositionChanged"/> event passing the <see cref="HexViewEventArgs"/> arguments.
+	/// </summary>
+	public virtual void OnPositionChanged ()
+	{
+		PositionChanged?.Invoke (this, new HexViewEventArgs (Position, CursorPosition, BytesPerLine));
+	}
 
-			if (leftSide) {
-				int value;
-				var k = (char)keyEvent.Key;
-				if (k >= 'A' && k <= 'F')
-					value = k - 'A' + 10;
-				else if (k >= 'a' && k <= 'f')
-					value = k - 'a' + 10;
-				else if (k >= '0' && k <= '9')
-					value = k - '0';
-				else
-					return false;
-
-				byte b;
-				if (!edits.TryGetValue (position, out b)) {
-					source.Position = position;
-					b = (byte)source.ReadByte ();
-				}
-				RedisplayLine (position);
-				if (firstNibble) {
-					firstNibble = false;
-					b = (byte)(b & 0xf | (value << bsize));
-					edits [position] = b;
-					OnEdited (new HexViewEditEventArgs (position, edits [position]));
-				} else {
-					b = (byte)(b & 0xf0 | value);
-					edits [position] = b;
-					OnEdited (new HexViewEditEventArgs (position, edits [position]));
-					MoveRight ();
-				}
-				return true;
-			} else
-				return false;
+	/// <inheritdoc/>
+	public override bool MouseEvent (MouseEvent me)
+	{
+		if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
+								&& !me.Flags.HasFlag (MouseFlags.WheeledDown) && !me.Flags.HasFlag (MouseFlags.WheeledUp)) {
+			return false;
 		}
 
-		/// <summary>
-		/// Method used to invoke the <see cref="Edited"/> event passing the <see cref="KeyValuePair{TKey, TValue}"/>.
-		/// </summary>
-		/// <param name="e">The key value pair.</param>
-		public virtual void OnEdited (HexViewEditEventArgs e)
-		{
-			Edited?.Invoke (this, e);
+		if (!HasFocus) {
+			SetFocus ();
 		}
 
-		/// <summary>
-		/// Method used to invoke the <see cref="PositionChanged"/> event passing the <see cref="HexViewEventArgs"/> arguments.
-		/// </summary>
-		public virtual void OnPositionChanged ()
-		{
-			PositionChanged?.Invoke (this, new HexViewEventArgs (Position, CursorPosition, BytesPerLine));
+		if (me.Flags == MouseFlags.WheeledDown) {
+			DisplayStart = Math.Min (DisplayStart + bytesPerLine, source.Length);
+			return true;
 		}
 
-		/// <inheritdoc/>
-		public override bool MouseEvent (MouseEvent me)
-		{
-			if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
-				&& !me.Flags.HasFlag (MouseFlags.WheeledDown) && !me.Flags.HasFlag (MouseFlags.WheeledUp))
-				return false;
+		if (me.Flags == MouseFlags.WheeledUp) {
+			DisplayStart = Math.Max (DisplayStart - bytesPerLine, 0);
+			return true;
+		}
 
-			if (!HasFocus)
-				SetFocus ();
+		if (me.X < displayWidth) {
+			return true;
+		}
+		int nblocks = bytesPerLine / bsize;
+		int blocksSize = nblocks * 14;
+		int blocksRightOffset = displayWidth + blocksSize - 1;
+		if (me.X > blocksRightOffset + bytesPerLine - 1) {
+			return true;
+		}
+		leftSide = me.X >= blocksRightOffset;
+		long lineStart = me.Y * bytesPerLine + displayStart;
+		int x = me.X - displayWidth + 1;
+		int block = x / 14;
+		x -= block * 2;
+		int empty = x % 3;
+		int item = x / 3;
+		if (!leftSide && item > 0 && (empty == 0 || x == block * 14 + 14 - 1 - block * 2)) {
+			return true;
+		}
+		firstNibble = true;
+		if (leftSide) {
+			position = Math.Min (lineStart + me.X - blocksRightOffset, source.Length);
+		} else {
+			position = Math.Min (lineStart + item, source.Length);
+		}
 
-			if (me.Flags == MouseFlags.WheeledDown) {
-				DisplayStart = Math.Min (DisplayStart + bytesPerLine, source.Length);
-				return true;
+		if (me.Flags == MouseFlags.Button1DoubleClicked) {
+			leftSide = !leftSide;
+			if (leftSide) {
+				firstNibble = empty == 1;
+			} else {
+				firstNibble = true;
 			}
+		}
+		SetNeedsDisplay ();
 
-			if (me.Flags == MouseFlags.WheeledUp) {
-				DisplayStart = Math.Max (DisplayStart - bytesPerLine, 0);
-				return true;
-			}
+		return true;
+	}
 
-			if (me.X < displayWidth)
-				return true;
-			var nblocks = bytesPerLine / bsize;
-			var blocksSize = nblocks * 14;
-			var blocksRightOffset = displayWidth + blocksSize - 1;
-			if (me.X > blocksRightOffset + bytesPerLine - 1)
-				return true;
-			leftSide = me.X >= blocksRightOffset;
-			var lineStart = (me.Y * bytesPerLine) + displayStart;
-			var x = me.X - displayWidth + 1;
-			var block = x / 14;
-			x -= block * 2;
-			var empty = x % 3;
-			var item = x / 3;
-			if (!leftSide && item > 0 && (empty == 0 || x == (block * 14) + 14 - 1 - (block * 2)))
-				return true;
-			firstNibble = true;
-			if (leftSide)
-				position = Math.Min (lineStart + me.X - blocksRightOffset, source.Length);
-			else
-				position = Math.Min (lineStart + item, source.Length);
-
-			if (me.Flags == MouseFlags.Button1DoubleClicked) {
-				leftSide = !leftSide;
-				if (leftSide)
-					firstNibble = empty == 1;
-				else
-					firstNibble = true;
-			}
-			SetNeedsDisplay ();
+	/// <summary>
+	/// Gets or sets whether this <see cref="HexView"/> allow editing of the <see cref="Stream"/> 
+	/// of the underlying <see cref="Stream"/>.
+	/// </summary>
+	/// <value><c>true</c> if allow edits; otherwise, <c>false</c>.</value>
+	public bool AllowEdits { get; set; } = true;
 
-			return true;
-		}
+	/// <summary>
+	/// Gets a <see cref="SortedDictionary{TKey, TValue}"/> describing the edits done to the <see cref="HexView"/>. 
+	/// Each Key indicates an offset where an edit was made and the Value is the changed byte.
+	/// </summary>
+	/// <value>The edits.</value>
+	public IReadOnlyDictionary<long, byte> Edits => edits;
 
-		/// <summary>
-		/// Gets or sets whether this <see cref="HexView"/> allow editing of the <see cref="Stream"/> 
-		/// of the underlying <see cref="Stream"/>.
-		/// </summary>
-		/// <value><c>true</c> if allow edits; otherwise, <c>false</c>.</value>
-		public bool AllowEdits { get; set; } = true;
-
-		/// <summary>
-		/// Gets a <see cref="SortedDictionary{TKey, TValue}"/> describing the edits done to the <see cref="HexView"/>. 
-		/// Each Key indicates an offset where an edit was made and the Value is the changed byte.
-		/// </summary>
-		/// <value>The edits.</value>
-		public IReadOnlyDictionary<long, byte> Edits => edits;
-
-		/// <summary>
-		/// Gets the current character position starting at one, related to the <see cref="Stream"/>.
-		/// </summary>
-		public long Position => position + 1;
-
-		/// <summary>
-		/// Gets the current cursor position starting at one for both, line and column.
-		/// </summary>
-		public Point CursorPosition {
-			get {
-				var delta = (int)position;
-				var line = delta / bytesPerLine + 1;
-				var item = delta % bytesPerLine + 1;
-
-				return new Point (item, line);
-			}
-		}
+	/// <summary>
+	/// Gets the current character position starting at one, related to the <see cref="Stream"/>.
+	/// </summary>
+	public long Position => position + 1;
 
-		/// <summary>
-		/// The bytes length per line.
-		/// </summary>
-		public int BytesPerLine => bytesPerLine;
+	/// <summary>
+	/// Gets the current cursor position starting at one for both, line and column.
+	/// </summary>
+	public Point CursorPosition {
+		get {
+			int delta = (int)position;
+			int line = delta / bytesPerLine + 1;
+			int item = delta % bytesPerLine + 1;
 
-		/// <summary>
-		/// This method applies and edits made to the <see cref="Stream"/> and resets the 
-		/// contents of the <see cref="Edits"/> property.
-		/// </summary>
-		/// <param name="stream">If provided also applies the changes to the passed <see cref="Stream"/></param>.
-		public void ApplyEdits (Stream stream = null)
-		{
-			foreach (var kv in edits) {
-				source.Position = kv.Key;
-				source.WriteByte (kv.Value);
-				source.Flush ();
-				if (stream != null) {
-					stream.Position = kv.Key;
-					stream.WriteByte (kv.Value);
-					stream.Flush ();
-				}
-			}
-			edits = new SortedDictionary<long, byte> ();
-			SetNeedsDisplay ();
+			return new Point (item, line);
 		}
+	}
 
-		/// <summary>
-		/// This method discards the edits made to the <see cref="Stream"/> by resetting the 
-		/// contents of the <see cref="Edits"/> property.
-		/// </summary>
-		public void DiscardEdits ()
-		{
-			edits = new SortedDictionary<long, byte> ();
+	/// <summary>
+	/// The bytes length per line.
+	/// </summary>
+	public int BytesPerLine => bytesPerLine;
+
+	/// <summary>
+	/// This method applies and edits made to the <see cref="Stream"/> and resets the 
+	/// contents of the <see cref="Edits"/> property.
+	/// </summary>
+	/// <param name="stream">If provided also applies the changes to the passed <see cref="Stream"/></param>.
+	public void ApplyEdits (Stream stream = null)
+	{
+		foreach (var kv in edits) {
+			source.Position = kv.Key;
+			source.WriteByte (kv.Value);
+			source.Flush ();
+			if (stream != null) {
+				stream.Position = kv.Key;
+				stream.WriteByte (kv.Value);
+				stream.Flush ();
+			}
 		}
+		edits = new SortedDictionary<long, byte> ();
+		SetNeedsDisplay ();
+	}
 
-		private CursorVisibility desiredCursorVisibility = CursorVisibility.Default;
+	/// <summary>
+	/// This method discards the edits made to the <see cref="Stream"/> by resetting the 
+	/// contents of the <see cref="Edits"/> property.
+	/// </summary>
+	public void DiscardEdits ()
+	{
+		edits = new SortedDictionary<long, byte> ();
+	}
 
-		/// <summary>
-		/// Get / Set the wished cursor when the field is focused
-		/// </summary>
-		public CursorVisibility DesiredCursorVisibility {
-			get => desiredCursorVisibility;
-			set {
-				if (desiredCursorVisibility != value && HasFocus) {
-					Application.Driver.SetCursorVisibility (value);
-				}
+	CursorVisibility desiredCursorVisibility = CursorVisibility.Default;
 
-				desiredCursorVisibility = value;
+	/// <summary>
+	/// Get / Set the wished cursor when the field is focused
+	/// </summary>
+	public CursorVisibility DesiredCursorVisibility {
+		get => desiredCursorVisibility;
+		set {
+			if (desiredCursorVisibility != value && HasFocus) {
+				Application.Driver.SetCursorVisibility (value);
 			}
+
+			desiredCursorVisibility = value;
 		}
+	}
 
-		///<inheritdoc/>
-		public override bool OnEnter (View view)
-		{
-			Application.Driver.SetCursorVisibility (DesiredCursorVisibility);
+	///<inheritdoc/>
+	public override bool OnEnter (View view)
+	{
+		Application.Driver.SetCursorVisibility (DesiredCursorVisibility);
 
-			return base.OnEnter (view);
-		}
+		return base.OnEnter (view);
 	}
-}
+}

+ 23 - 17
Terminal.Gui/Views/Label.cs

@@ -58,12 +58,31 @@ namespace Terminal.Gui {
 		{
 			Height = 1;
 			AutoSize = autosize;
-			//HotKeySpecifier = new Rune ('_');
-			//if (HotKey != Key.Null) {
-			//	AddKeyBinding (Key.Space | HotKey, Command.Accept);
-			//}
+			// Things this view knows how to do
+			AddCommand (Command.Default, () => {
+				// BUGBUG: This is a hack, but it does work.
+				var can = CanFocus;
+				CanFocus = true;
+				SetFocus ();
+				SuperView.FocusNext ();
+				CanFocus = can;
+				return true;
+			});
+			AddCommand (Command.Accept, () => AcceptKey ());
+
+			// Default key bindings for this view
+			KeyBindings.Add (KeyCode.Space, Command.Accept);
 		}
 
+		bool AcceptKey ()
+		{
+			if (!HasFocus) {
+				SetFocus ();
+			}
+			OnClicked ();
+			return true;
+		}
+		
 		/// <summary>
 		///   The event fired when the user clicks the primary mouse button within the Bounds of this <see cref="View"/>
 		///   or if the user presses the action key while this view is focused. (TODO: IsDefault)
@@ -111,19 +130,6 @@ namespace Terminal.Gui {
 			return base.OnEnter (view);
 		}
 
-		///<inheritdoc/>
-		public override bool ProcessHotKey (KeyEvent ke)
-		{
-			if (ke.Key == (Key.AltMask | HotKey)) {
-				if (!HasFocus) {
-					SetFocus ();
-				}
-				OnClicked ();
-				return true;
-			}
-			return base.ProcessHotKey (ke);
-		}
-
 		/// <summary>
 		/// Virtual method to invoke the <see cref="Clicked"/> event.
 		/// </summary>

+ 15 - 25
Terminal.Gui/Views/ListView.cs

@@ -166,9 +166,9 @@ namespace Terminal.Gui {
 			set {
 				allowsMarking = value;
 				if (allowsMarking) {
-					AddKeyBinding (Key.Space, Command.ToggleChecked);
+					KeyBindings.Add (KeyCode.Space, Command.ToggleChecked);
 				} else {
-					ClearKeyBinding (Key.Space);
+					KeyBindings.Remove (KeyCode.Space);
 				}
 
 				SetNeedsDisplay ();
@@ -330,22 +330,22 @@ namespace Terminal.Gui {
 			AddCommand (Command.ToggleChecked, () => MarkUnmarkRow ());
 
 			// Default keybindings for all ListViews
-			AddKeyBinding (Key.CursorUp, Command.LineUp);
-			AddKeyBinding (Key.P | Key.CtrlMask, Command.LineUp);
+			KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
+			KeyBindings.Add (KeyCode.P | KeyCode.CtrlMask, Command.LineUp);
 
-			AddKeyBinding (Key.CursorDown, Command.LineDown);
-			AddKeyBinding (Key.N | Key.CtrlMask, Command.LineDown);
+			KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
+			KeyBindings.Add (KeyCode.N | KeyCode.CtrlMask, Command.LineDown);
 
-			AddKeyBinding (Key.PageUp, Command.PageUp);
+			KeyBindings.Add (KeyCode.PageUp, Command.PageUp);
 
-			AddKeyBinding (Key.PageDown, Command.PageDown);
-			AddKeyBinding (Key.V | Key.CtrlMask, Command.PageDown);
+			KeyBindings.Add (KeyCode.PageDown, Command.PageDown);
+			KeyBindings.Add (KeyCode.V | KeyCode.CtrlMask, Command.PageDown);
 
-			AddKeyBinding (Key.Home, Command.TopHome);
+			KeyBindings.Add (KeyCode.Home, Command.TopHome);
 
-			AddKeyBinding (Key.End, Command.BottomEnd);
+			KeyBindings.Add (KeyCode.End, Command.BottomEnd);
 
-			AddKeyBinding (Key.Enter, Command.OpenSelectedItem);
+			KeyBindings.Add (KeyCode.Enter, Command.OpenSelectedItem);
 		}
 
 		///<inheritdoc/>
@@ -416,20 +416,11 @@ namespace Terminal.Gui {
 		public CollectionNavigator KeystrokeNavigator { get; private set; } = new CollectionNavigator ();
 
 		///<inheritdoc/>
-		public override bool ProcessKey (KeyEvent kb)
+		public override bool OnProcessKeyDown (Key a)
 		{
-			if (source == null) {
-				return base.ProcessKey (kb);
-			}
-
-			var result = InvokeKeybindings (kb);
-			if (result != null) {
-				return (bool)result;
-			}
-
 			// Enable user to find & select an item by typing text
-			if (CollectionNavigator.IsCompatibleKey (kb)) {
-				var newItem = KeystrokeNavigator?.GetNextMatchingItem (SelectedItem, (char)kb.KeyValue);
+			if (CollectionNavigator.IsCompatibleKey (a)) {
+				var newItem = KeystrokeNavigator?.GetNextMatchingItem (SelectedItem, (char)a);
 				if (newItem is int && newItem != -1) {
 					SelectedItem = (int)newItem;
 					EnsureSelectedItemVisible ();
@@ -437,7 +428,6 @@ namespace Terminal.Gui {
 					return true;
 				}
 			}
-
 			return false;
 		}
 

+ 0 - 2215
Terminal.Gui/Views/Menu.cs

@@ -1,2215 +0,0 @@
-using System;
-using System.Text;
-using System.Linq;
-using System.Collections.Generic;
-
-namespace Terminal.Gui {
-
-	/// <summary>
-	/// Specifies how a <see cref="MenuItem"/> shows selection state. 
-	/// </summary>
-	[Flags]
-	public enum MenuItemCheckStyle {
-		/// <summary>
-		/// The menu item will be shown normally, with no check indicator. The default.
-		/// </summary>
-		NoCheck = 0b_0000_0000,
-
-		/// <summary>
-		/// The menu item will indicate checked/un-checked state (see <see cref="Checked"/>).
-		/// </summary>
-		Checked = 0b_0000_0001,
-
-		/// <summary>
-		/// The menu item is part of a menu radio group (see <see cref="Checked"/>) and will indicate selected state.
-		/// </summary>
-		Radio = 0b_0000_0010,
-	};
-
-	/// <summary>
-	/// A <see cref="MenuItem"/> has title, an associated help text, and an action to execute on activation. 
-	/// MenuItems can also have a checked indicator (see <see cref="Checked"/>).
-	/// </summary>
-	public class MenuItem {
-		string title;
-		ShortcutHelper shortcutHelper;
-		bool allowNullChecked;
-		MenuItemCheckStyle checkType;
-
-		internal int TitleLength => GetMenuBarItemLength (Title);
-
-		/// <summary>
-		/// Gets or sets arbitrary data for the menu item.
-		/// </summary>
-		/// <remarks>This property is not used internally.</remarks>
-		public object Data { get; set; }
-
-		/// <summary>
-		/// Initializes a new instance of <see cref="MenuItem"/>
-		/// </summary>
-		public MenuItem (Key shortcut = Key.Null) : this ("", "", null, null, null, shortcut) { }
-
-		/// <summary>
-		/// Initializes a new instance of <see cref="MenuItem"/>.
-		/// </summary>
-		/// <param name="title">Title for the menu item.</param>
-		/// <param name="help">Help text to display.</param>
-		/// <param name="action">Action to invoke when the menu item is activated.</param>
-		/// <param name="canExecute">Function to determine if the action can currently be executed.</param>
-		/// <param name="parent">The <see cref="Parent"/> of this menu item.</param>
-		/// <param name="shortcut">The <see cref="Shortcut"/> keystroke combination.</param>
-		public MenuItem (string title, string help, Action action, Func<bool> canExecute = null, MenuItem parent = null, Key shortcut = Key.Null)
-		{
-			Title = title ?? "";
-			Help = help ?? "";
-			Action = action;
-			CanExecute = canExecute;
-			Parent = parent;
-			shortcutHelper = new ShortcutHelper ();
-			if (shortcut != Key.Null) {
-				shortcutHelper.Shortcut = shortcut;
-			}
-		}
-
-		/// <summary>
-		/// The HotKey is used to activate a <see cref="MenuItem"/> with the keyboard. HotKeys are defined by prefixing the <see cref="Title"/>
-		/// of a MenuItem with an underscore ('_'). 
-		/// <para>
-		/// Pressing Alt-Hotkey for a <see cref="MenuBarItem"/> (menu items on the menu bar) works even if the menu is not active). 
-		/// Once a menu has focus and is active, pressing just the HotKey will activate the MenuItem.
-		/// </para>
-		/// <para>
-		/// For example for a MenuBar with a "_File" MenuBarItem that contains a "_New" MenuItem, Alt-F will open the File menu.
-		/// Pressing the N key will then activate the New MenuItem.
-		/// </para>
-		/// <para>
-		/// See also <see cref="Shortcut"/> which enable global key-bindings to menu items.
-		/// </para>
-		/// </summary>
-		public Rune HotKey;
-
-		/// <summary>
-		/// Shortcut defines a key binding to the MenuItem that will invoke the MenuItem's action globally for the <see cref="View"/> that is
-		/// the parent of the <see cref="MenuBar"/> or <see cref="ContextMenu"/> this <see cref="MenuItem"/>.
-		/// <para>
-		/// The <see cref="Key"/> will be drawn on the MenuItem to the right of the <see cref="Title"/> and <see cref="Help"/> text. See <see cref="ShortcutTag"/>.
-		/// </para>
-		/// </summary>
-		public Key Shortcut {
-			get => shortcutHelper.Shortcut;
-			set {
-				if (shortcutHelper.Shortcut != value && (ShortcutHelper.PostShortcutValidation (value) || value == Key.Null)) {
-					shortcutHelper.Shortcut = value;
-				}
-			}
-		}
-
-		/// <summary>
-		/// Gets the text describing the keystroke combination defined by <see cref="Shortcut"/>.
-		/// </summary>
-		public string ShortcutTag => ShortcutHelper.GetShortcutTag (shortcutHelper.Shortcut);
-
-		/// <summary>
-		/// Gets or sets the title of the menu item .
-		/// </summary>
-		/// <value>The title.</value>
-		public string Title {
-			get { return title; }
-			set {
-				if (title != value) {
-					title = value;
-					GetHotKey ();
-				}
-			}
-		}
-
-		/// <summary>
-		/// Gets or sets the help text for the menu item. The help text is drawn to the right of the <see cref="Title"/>.
-		/// </summary>
-		/// <value>The help text.</value>
-		public string Help { get; set; }
-
-		/// <summary>
-		/// Gets or sets the action to be invoked when the menu item is triggered.
-		/// </summary>
-		/// <value>Method to invoke.</value>
-		public Action Action { get; set; }
-
-		/// <summary>
-		/// Gets or sets the action to be invoked to determine if the menu can be triggered. If <see cref="CanExecute"/> returns <see langword="true"/>
-		/// the menu item will be enabled. Otherwise, it will be disabled. 
-		/// </summary>
-		/// <value>Function to determine if the action is can be executed or not.</value>
-		public Func<bool> CanExecute { get; set; }
-
-		/// <summary>
-		/// Returns <see langword="true"/> if the menu item is enabled. This method is a wrapper around <see cref="CanExecute"/>.
-		/// </summary>
-		public bool IsEnabled ()
-		{
-			return CanExecute == null ? true : CanExecute ();
-		}
-
-		// 
-		// ┌─────────────────────────────┐
-		// │ Quit  Quit UI Catalog  Ctrl+Q │
-		// └─────────────────────────────┘
-		// ┌─────────────────┐
-		// │ ◌ TopLevel Alt+T │
-		// └─────────────────┘
-		// TODO: Replace the `2` literals with named constants 
-		internal int Width => 1 + // space before Title
-			TitleLength +
-			2 + // space after Title - BUGBUG: This should be 1 
-			(Checked == true || CheckType.HasFlag (MenuItemCheckStyle.Checked) || CheckType.HasFlag (MenuItemCheckStyle.Radio) ? 2 : 0) + // check glyph + space 
-			(Help.GetColumns () > 0 ? 2 + Help.GetColumns () : 0) + // Two spaces before Help
-			(ShortcutTag.GetColumns () > 0 ? 2 + ShortcutTag.GetColumns () : 0); // Pad two spaces before shortcut tag (which are also aligned right)
-
-		/// <summary>
-		/// Sets or gets whether the <see cref="MenuItem"/> shows a check indicator or not. See <see cref="MenuItemCheckStyle"/>.
-		/// </summary>
-		public bool? Checked { set; get; }
-
-		/// <summary>
-		/// Used only if <see cref="CheckType"/> is of <see cref="MenuItemCheckStyle.Checked"/> type.
-		/// If <see langword="true"/> allows <see cref="Checked"/> to be null, true or false.
-		/// If <see langword="false"/> only allows <see cref="Checked"/> to be true or false.
-		/// </summary>
-		public bool AllowNullChecked {
-			get => allowNullChecked;
-			set {
-				allowNullChecked = value;
-				if (Checked == null) {
-					Checked = false;
-				}
-			}
-		}
-
-		/// <summary>
-		/// Sets or gets the <see cref="MenuItemCheckStyle"/> of a menu item where <see cref="Checked"/> is set to <see langword="true"/>.
-		/// </summary>
-		public MenuItemCheckStyle CheckType {
-			get => checkType;
-			set {
-				checkType = value;
-				if (checkType == MenuItemCheckStyle.Checked && !allowNullChecked && Checked == null) {
-					Checked = false;
-				}
-			}
-		}
-
-		/// <summary>
-		/// Gets the parent for this <see cref="MenuItem"/>.
-		/// </summary>
-		/// <value>The parent.</value>
-		public MenuItem Parent { get; set; }
-
-		/// <summary>
-		/// Gets if this <see cref="MenuItem"/> is from a sub-menu.
-		/// </summary>
-		internal bool IsFromSubMenu { get { return Parent != null; } }
-
-		/// <summary>
-		/// Merely a debugging aid to see the interaction with main.
-		/// </summary>
-		public MenuItem GetMenuItem ()
-		{
-			return this;
-		}
-
-		/// <summary>
-		/// Merely a debugging aid to see the interaction with main.
-		/// </summary>
-		public bool GetMenuBarItem ()
-		{
-			return IsFromSubMenu;
-		}
-
-		/// <summary>
-		/// Toggle the <see cref="Checked"/> between three states if <see cref="AllowNullChecked"/> is <see langword="true"/>
-		/// or between two states if <see cref="AllowNullChecked"/> is <see langword="false"/>.
-		/// </summary>
-		public void ToggleChecked ()
-		{
-			if (checkType != MenuItemCheckStyle.Checked) {
-				throw new InvalidOperationException ("This isn't a Checked MenuItemCheckStyle!");
-			}
-			var previousChecked = Checked;
-			if (AllowNullChecked) {
-				switch (previousChecked) {
-				case null:
-					Checked = true;
-					break;
-				case true:
-					Checked = false;
-					break;
-				case false:
-					Checked = null;
-					break;
-				}
-			} else {
-				Checked = !Checked;
-			}
-		}
-
-		void GetHotKey ()
-		{
-			bool nextIsHot = false;
-			foreach (var x in title) {
-				if (x == MenuBar.HotKeySpecifier.Value) {
-					nextIsHot = true;
-				} else {
-					if (nextIsHot) {
-						HotKey = (Rune)Char.ToUpper ((char)x);
-						break;
-					}
-					nextIsHot = false;
-					HotKey = default;
-				}
-			}
-		}
-
-		int GetMenuBarItemLength (string title)
-		{
-			int len = 0;
-			foreach (var ch in title.EnumerateRunes ()) {
-				if (ch == MenuBar.HotKeySpecifier)
-					continue;
-				len += Math.Max (ch.GetColumns (), 1);
-			}
-
-			return len;
-		}
-	}
-
-	/// <summary>
-	/// <see cref="MenuBarItem"/> is a menu item on an app's <see cref="MenuBar"/>. 
-	/// MenuBarItems do not support <see cref="MenuItem.Shortcut"/>.
-	/// </summary>
-	public class MenuBarItem : MenuItem {
-		/// <summary>
-		/// Initializes a new <see cref="MenuBarItem"/> as a <see cref="MenuItem"/>.
-		/// </summary>
-		/// <param name="title">Title for the menu item.</param>
-		/// <param name="help">Help text to display. Will be displayed next to the Title surrounded by parentheses.</param>
-		/// <param name="action">Action to invoke when the menu item is activated.</param>
-		/// <param name="canExecute">Function to determine if the action can currently be executed.</param>
-		/// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
-		public MenuBarItem (string title, string help, Action action, Func<bool> canExecute = null, MenuItem parent = null) : base (title, help, action, canExecute, parent)
-		{
-			Initialize (title, null, null, true);
-		}
-
-		/// <summary>
-		/// Initializes a new <see cref="MenuBarItem"/>.
-		/// </summary>
-		/// <param name="title">Title for the menu item.</param>
-		/// <param name="children">The items in the current menu.</param>
-		/// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
-		public MenuBarItem (string title, MenuItem [] children, MenuItem parent = null)
-		{
-			Initialize (title, children, parent);
-		}
-
-		/// <summary>
-		/// Initializes a new <see cref="MenuBarItem"/> with separate list of items.
-		/// </summary>
-		/// <param name="title">Title for the menu item.</param>
-		/// <param name="children">The list of items in the current menu.</param>
-		/// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
-		public MenuBarItem (string title, List<MenuItem []> children, MenuItem parent = null)
-		{
-			Initialize (title, children, parent);
-		}
-
-		/// <summary>
-		/// Initializes a new <see cref="MenuBarItem"/>.
-		/// </summary>
-		/// <param name="children">The items in the current menu.</param>
-		public MenuBarItem (MenuItem [] children) : this ("", children) { }
-
-		/// <summary>
-		/// Initializes a new <see cref="MenuBarItem"/>.
-		/// </summary>
-		public MenuBarItem () : this (children: new MenuItem [] { }) { }
-
-		void Initialize (string title, object children, MenuItem parent = null, bool isTopLevel = false)
-		{
-			if (!isTopLevel && children == null) {
-				throw new ArgumentNullException (nameof (children), "The parameter cannot be null. Use an empty array instead.");
-			}
-			SetTitle (title ?? "");
-			if (parent != null) {
-				Parent = parent;
-			}
-			if (children is List<MenuItem []>) {
-				MenuItem [] childrens = new MenuItem [] { };
-				foreach (var item in (List<MenuItem []>)children) {
-					for (int i = 0; i < item.Length; i++) {
-						SetChildrensParent (item);
-						Array.Resize (ref childrens, childrens.Length + 1);
-						childrens [childrens.Length - 1] = item [i];
-					}
-				}
-				Children = childrens;
-			} else if (children is MenuItem []) {
-				SetChildrensParent ((MenuItem [])children);
-				Children = (MenuItem [])children;
-			} else {
-				Children = null;
-			}
-		}
-
-		void SetChildrensParent (MenuItem [] childrens)
-		{
-			foreach (var child in childrens) {
-				if (child != null && child.Parent == null) {
-					child.Parent = this;
-				}
-			}
-		}
-
-		/// <summary>
-		/// Check if the children parameter is a <see cref="MenuBarItem"/>.
-		/// </summary>
-		/// <param name="children"></param>
-		/// <returns>Returns a <see cref="MenuBarItem"/> or null otherwise.</returns>
-		public MenuBarItem SubMenu (MenuItem children)
-		{
-			return children as MenuBarItem;
-		}
-
-		/// <summary>
-		/// Check if the <see cref="MenuItem"/> parameter is a child of this.
-		/// </summary>
-		/// <param name="menuItem"></param>
-		/// <returns>Returns <c>true</c> if it is a child of this. <c>false</c> otherwise.</returns>
-		public bool IsSubMenuOf (MenuItem menuItem)
-		{
-			foreach (var child in Children) {
-				if (child == menuItem && child.Parent == menuItem.Parent) {
-					return true;
-				}
-			}
-			return false;
-		}
-
-		/// <summary>
-		/// Get the index of the <see cref="MenuItem"/> parameter.
-		/// </summary>
-		/// <param name="children"></param>
-		/// <returns>Returns a value bigger than -1 if the <see cref="MenuItem"/> is a child of this.</returns>
-		public int GetChildrenIndex (MenuItem children)
-		{
-			if (Children?.Length == 0) {
-				return -1;
-			}
-			int i = 0;
-			foreach (var child in Children) {
-				if (child == children) {
-					return i;
-				}
-				i++;
-			}
-			return -1;
-		}
-
-		void SetTitle (string title)
-		{
-			if (title == null)
-				title = string.Empty;
-			Title = title;
-		}
-
-		/// <summary>
-		/// Gets or sets an array of <see cref="MenuItem"/> objects that are the children of this <see cref="MenuBarItem"/>
-		/// </summary>
-		/// <value>The children.</value>
-		public MenuItem [] Children { get; set; }
-
-		internal bool IsTopLevel { get => Parent == null && (Children == null || Children.Length == 0) && Action != null; }
-	}
-
-	class Menu : View {
-		internal MenuBarItem barItems;
-		internal MenuBar host;
-		internal int current;
-		internal View previousSubFocused;
-
-		internal static Rect MakeFrame (int x, int y, MenuItem [] items, Menu parent = null, LineStyle border = LineStyle.Single)
-		{
-			if (items == null || items.Length == 0) {
-				return new Rect ();
-			}
-			int minX = x;
-			int minY = y;
-			var borderOffset = 2; // This 2 is for the space around
-			int maxW = (items.Max (z => z?.Width) ?? 0) + borderOffset;
-			int maxH = items.Length + borderOffset;
-			if (parent != null && x + maxW > Driver.Cols) {
-				minX = Math.Max (parent.Frame.Right - parent.Frame.Width - maxW, 0);
-			}
-			if (y + maxH > Driver.Rows) {
-				minY = Math.Max (Driver.Rows - maxH, 0);
-			}
-			return new Rect (minX, minY, maxW, maxH);
-		}
-
-		public Menu (MenuBar host, int x, int y, MenuBarItem barItems, Menu parent = null, LineStyle border = LineStyle.Single)
-			: base (MakeFrame (x, y, barItems.Children, parent, border))
-		{
-			this.barItems = barItems;
-			this.host = host;
-			if (barItems.IsTopLevel) {
-				// This is a standalone MenuItem on a MenuBar
-				ColorScheme = host.ColorScheme;
-				CanFocus = true;
-			} else {
-
-				current = -1;
-				for (int i = 0; i < barItems.Children?.Length; i++) {
-					if (barItems.Children [i]?.IsEnabled () == true) {
-						current = i;
-						break;
-					}
-				}
-				ColorScheme = host.ColorScheme;
-				CanFocus = true;
-				WantMousePositionReports = host.WantMousePositionReports;
-			}
-
-			BorderStyle = host.MenusBorderStyle;
-
-			if (Application.Current != null) {
-				Application.Current.DrawContentComplete += Current_DrawContentComplete;
-				Application.Current.SizeChanging += Current_TerminalResized;
-			}
-			Application.MouseEvent += Application_RootMouseEvent;
-
-			// Things this view knows how to do
-			AddCommand (Command.LineUp, () => MoveUp ());
-			AddCommand (Command.LineDown, () => MoveDown ());
-			AddCommand (Command.Left, () => { this.host.PreviousMenu (true); return true; });
-			AddCommand (Command.Right, () => {
-				this.host.NextMenu (!this.barItems.IsTopLevel || (this.barItems.Children != null
-					&& this.barItems.Children.Length > 0 && current > -1
-					&& current < this.barItems.Children.Length && this.barItems.Children [current].IsFromSubMenu),
-					this.barItems.Children != null && this.barItems.Children.Length > 0 && current > -1
-					&& host.UseSubMenusSingleFrame && this.barItems.SubMenu (this.barItems.Children [current]) != null);
-
-				return true;
-			});
-			AddCommand (Command.Cancel, () => { CloseAllMenus (); return true; });
-			AddCommand (Command.Accept, () => { RunSelected (); return true; });
-
-			// Default keybindings for this view
-			AddKeyBinding (Key.CursorUp, Command.LineUp);
-			AddKeyBinding (Key.CursorDown, Command.LineDown);
-			AddKeyBinding (Key.CursorLeft, Command.Left);
-			AddKeyBinding (Key.CursorRight, Command.Right);
-			AddKeyBinding (Key.Esc, Command.Cancel);
-			AddKeyBinding (Key.Enter, Command.Accept);
-		}
-
-		private void Current_TerminalResized (object sender, SizeChangedEventArgs e)
-		{
-			if (host.IsMenuOpen) {
-				host.CloseAllMenus ();
-			}
-		}
-
-		/// <inheritdoc/>
-		public override void OnVisibleChanged ()
-		{
-			base.OnVisibleChanged ();
-			if (Visible) {
-				Application.MouseEvent += Application_RootMouseEvent;
-			} else {
-				Application.MouseEvent -= Application_RootMouseEvent;
-			}
-		}
-
-		private void Application_RootMouseEvent (object sender, MouseEventEventArgs a)
-		{
-			if (a.MouseEvent.View is MenuBar) {
-				return;
-			}
-			var locationOffset = host.GetScreenOffsetFromCurrent ();
-			if (SuperView != null && SuperView != Application.Current) {
-				locationOffset.X += SuperView.Border.Thickness.Left;
-				locationOffset.Y += SuperView.Border.Thickness.Top;
-			}
-			var view = View.FindDeepestView (this, a.MouseEvent.X + locationOffset.X, a.MouseEvent.Y + locationOffset.Y, out int rx, out int ry);
-			if (view == this) {
-				if (!Visible) {
-					throw new InvalidOperationException ("This shouldn't running on a invisible menu!");
-				}
-
-				var nme = new MouseEvent () {
-					X = rx,
-					Y = ry,
-					Flags = a.MouseEvent.Flags,
-					View = view
-				};
-				if (MouseEvent (nme) || a.MouseEvent.Flags == MouseFlags.Button1Pressed || a.MouseEvent.Flags == MouseFlags.Button1Released) {
-					a.MouseEvent.Handled = true;
-				}
-			}
-		}
-
-		internal Attribute DetermineColorSchemeFor (MenuItem item, int index)
-		{
-			if (item != null) {
-				if (index == current) return ColorScheme.Focus;
-				if (!item.IsEnabled ()) return ColorScheme.Disabled;
-			}
-			return GetNormalColor ();
-		}
-
-		public override void OnDrawContent (Rect contentArea)
-		{
-			if (barItems.Children == null) {
-				return;
-			}
-			var savedClip = Driver.Clip;
-			Driver.Clip = new Rect (0, 0, Driver.Cols, Driver.Rows);
-			Driver.SetAttribute (GetNormalColor ());
-
-			OnDrawFrames ();
-			OnRenderLineCanvas ();
-
-			for (int i = Bounds.Y; i < barItems.Children.Length; i++) {
-				if (i < 0) {
-					continue;
-				}
-				if (BoundsToScreen (Bounds).Y + i >= Driver.Rows) {
-					break;
-				}
-				var item = barItems.Children [i];
-				Driver.SetAttribute (item == null ? GetNormalColor ()
-					: i == current ? ColorScheme.Focus : GetNormalColor ());
-				if (item == null && BorderStyle != LineStyle.None) {
-					Move (-1, i);
-					Driver.AddRune (CM.Glyphs.LeftTee);
-				} else if (Frame.X < Driver.Cols) {
-					Move (0, i);
-				}
-
-				Driver.SetAttribute (DetermineColorSchemeFor (item, i));
-				for (int p = Bounds.X; p < Frame.Width - 2; p++) { // This - 2 is for the border
-					if (p < 0) {
-						continue;
-					}
-					if (BoundsToScreen (Bounds).X + p >= Driver.Cols) {
-						break;
-					}
-					if (item == null)
-						Driver.AddRune (CM.Glyphs.HLine);
-					else if (i == 0 && p == 0 && host.UseSubMenusSingleFrame && item.Parent.Parent != null)
-						Driver.AddRune (CM.Glyphs.LeftArrow);
-					// This `- 3` is left border + right border + one row in from right
-					else if (p == Frame.Width - 3 && barItems.SubMenu (barItems.Children [i]) != null)
-						Driver.AddRune (CM.Glyphs.RightArrow);
-					else
-						Driver.AddRune ((Rune)' ');
-				}
-
-				if (item == null) {
-					if (BorderStyle != LineStyle.None && SuperView?.Frame.Right - Frame.X > Frame.Width) {
-						Move (Frame.Width - 2, i);
-						Driver.AddRune (CM.Glyphs.RightTee);
-					}
-					continue;
-				}
-
-				string textToDraw = null;
-				var nullCheckedChar = CM.Glyphs.NullChecked;
-				var checkChar = CM.Glyphs.Selected;
-				var uncheckedChar = CM.Glyphs.UnSelected;
-
-				if (item.CheckType.HasFlag (MenuItemCheckStyle.Checked)) {
-					checkChar = CM.Glyphs.Checked;
-					uncheckedChar = CM.Glyphs.UnChecked;
-				}
-
-				// Support Checked even though CheckType wasn't set
-				if (item.CheckType == MenuItemCheckStyle.Checked && item.Checked == null) {
-					textToDraw = $"{nullCheckedChar} {item.Title}";
-				} else if (item.Checked == true) {
-					textToDraw = $"{checkChar} {item.Title}";
-				} else if (item.CheckType.HasFlag (MenuItemCheckStyle.Checked) || item.CheckType.HasFlag (MenuItemCheckStyle.Radio)) {
-					textToDraw = $"{uncheckedChar} {item.Title}";
-				} else {
-					textToDraw = item.Title;
-				}
-
-				BoundsToScreen (0, i, out int vtsCol, out int vtsRow, false);
-				if (vtsCol < Driver.Cols) {
-					Driver.Move (vtsCol + 1, vtsRow);
-					if (!item.IsEnabled ()) {
-						DrawHotString (textToDraw, ColorScheme.Disabled, ColorScheme.Disabled);
-					} else if (i == 0 && host.UseSubMenusSingleFrame && item.Parent.Parent != null) {
-						var tf = new TextFormatter () {
-							Alignment = TextAlignment.Centered,
-							HotKeySpecifier = MenuBar.HotKeySpecifier,
-							Text = textToDraw
-						};
-						// The -3 is left/right border + one space (not sure what for)
-						tf.Draw (BoundsToScreen (new Rect (1, i, Frame.Width - 3, 1)),
-							i == current ? ColorScheme.Focus : GetNormalColor (),
-							i == current ? ColorScheme.HotFocus : ColorScheme.HotNormal,
-							SuperView == null ? default : SuperView.BoundsToScreen (SuperView.Bounds));
-					} else {
-						DrawHotString (textToDraw,
-							i == current ? ColorScheme.HotFocus : ColorScheme.HotNormal,
-							i == current ? ColorScheme.Focus : GetNormalColor ());
-					}
-
-					// The help string
-					var l = item.ShortcutTag.GetColumns () == 0 ? item.Help.GetColumns () : item.Help.GetColumns () + item.ShortcutTag.GetColumns () + 2;
-					var col = Frame.Width - l - 3;
-					BoundsToScreen (col, i, out vtsCol, out vtsRow, false);
-					if (vtsCol < Driver.Cols) {
-						Driver.Move (vtsCol, vtsRow);
-						Driver.AddStr (item.Help);
-
-						// The shortcut tag string
-						if (!string.IsNullOrEmpty (item.ShortcutTag)) {
-							Driver.Move (vtsCol + l - item.ShortcutTag.GetColumns (), vtsRow);
-							Driver.AddStr (item.ShortcutTag);
-						}
-					}
-				}
-			}
-			Driver.Clip = savedClip;
-
-			PositionCursor ();
-		}
-
-		private void Current_DrawContentComplete (object sender, DrawEventArgs e)
-		{
-			if (Visible) {
-				OnDrawContent (Bounds);
-			}
-		}
-
-		public override void PositionCursor ()
-		{
-			if (host == null || host.IsMenuOpen)
-				if (barItems.IsTopLevel) {
-					host.PositionCursor ();
-				} else
-					Move (2, 1 + current);
-			else
-				host.PositionCursor ();
-		}
-
-		public void Run (Action action)
-		{
-			if (action == null || host == null)
-				return;
-
-			Application.UngrabMouse ();
-			host.CloseAllMenus ();
-			Application.Refresh ();
-
-			host.Run (action);
-		}
-
-		public override bool OnLeave (View view)
-		{
-			return host.OnLeave (view);
-		}
-
-		public override bool OnKeyDown (KeyEvent keyEvent)
-		{
-			if (keyEvent.IsAlt) {
-				host.CloseAllMenus ();
-				return true;
-			}
-
-			return false;
-		}
-
-		public override bool ProcessHotKey (KeyEvent keyEvent)
-		{
-			// To ncurses simulate a AltMask key pressing Alt+Space because
-			// it can't detect an alone special key down was pressed.
-			if (keyEvent.IsAlt && keyEvent.Key == Key.AltMask) {
-				OnKeyDown (keyEvent);
-				return true;
-			}
-
-			return false;
-		}
-
-		public override bool ProcessKey (KeyEvent kb)
-		{
-			var result = InvokeKeybindings (kb);
-			if (result != null)
-				return (bool)result;
-
-			// TODO: rune-ify
-			if (barItems.Children != null && Char.IsLetterOrDigit ((char)kb.KeyValue)) {
-				var x = Char.ToUpper ((char)kb.KeyValue);
-				var idx = -1;
-				foreach (var item in barItems.Children) {
-					idx++;
-					if (item == null) continue;
-					if (item.IsEnabled () && item.HotKey.Value == x) {
-						current = idx;
-						RunSelected ();
-						return true;
-					}
-				}
-			}
-			return host.ProcessHotKey (kb);
-		}
-
-		void RunSelected ()
-		{
-			if (barItems.IsTopLevel) {
-				Run (barItems.Action);
-			} else if (current > -1 && barItems.Children [current].Action != null) {
-				Run (barItems.Children [current].Action);
-			} else if (current == 0 && host.UseSubMenusSingleFrame
-				&& barItems.Children [current].Parent.Parent != null) {
-
-				host.PreviousMenu (barItems.Children [current].Parent.IsFromSubMenu, true);
-			} else if (current > -1 && barItems.SubMenu (barItems.Children [current]) != null) {
-
-				CheckSubMenu ();
-			}
-		}
-
-		void CloseAllMenus ()
-		{
-			Application.UngrabMouse ();
-			host.CloseAllMenus ();
-		}
-
-		bool MoveDown ()
-		{
-			if (barItems.IsTopLevel) {
-				return true;
-			}
-			bool disabled;
-			do {
-				current++;
-				if (current >= barItems.Children.Length) {
-					current = 0;
-				}
-				if (this != host.openCurrentMenu && barItems.Children [current]?.IsFromSubMenu == true && host.selectedSub > -1) {
-					host.PreviousMenu (true);
-					host.SelectEnabledItem (barItems.Children, current, out current);
-					host.openCurrentMenu = this;
-				}
-				var item = barItems.Children [current];
-				if (item?.IsEnabled () != true) {
-					disabled = true;
-				} else {
-					disabled = false;
-				}
-				if (!host.UseSubMenusSingleFrame && host.UseKeysUpDownAsKeysLeftRight && barItems.SubMenu (barItems.Children [current]) != null &&
-					!disabled && host.IsMenuOpen) {
-					if (!CheckSubMenu ())
-						return false;
-					break;
-				}
-				if (!host.IsMenuOpen) {
-					host.OpenMenu (host.selected);
-				}
-			} while (barItems.Children [current] == null || disabled);
-			SetNeedsDisplay ();
-			SetParentSetNeedsDisplay ();
-			if (!host.UseSubMenusSingleFrame)
-				host.OnMenuOpened ();
-			return true;
-		}
-
-		bool MoveUp ()
-		{
-			if (barItems.IsTopLevel || current == -1) {
-				return true;
-			}
-			bool disabled;
-			do {
-				current--;
-				if (host.UseKeysUpDownAsKeysLeftRight && !host.UseSubMenusSingleFrame) {
-					if ((current == -1 || this != host.openCurrentMenu) && barItems.Children [current + 1].IsFromSubMenu && host.selectedSub > -1) {
-						current++;
-						host.PreviousMenu (true);
-						if (current > 0) {
-							current--;
-							host.openCurrentMenu = this;
-						}
-						break;
-					}
-				}
-				if (current < 0)
-					current = barItems.Children.Length - 1;
-				if (!host.SelectEnabledItem (barItems.Children, current, out current, false)) {
-					current = 0;
-					if (!host.SelectEnabledItem (barItems.Children, current, out current) && !host.CloseMenu (false)) {
-						return false;
-					}
-					break;
-				}
-				var item = barItems.Children [current];
-				if (item?.IsEnabled () != true) {
-					disabled = true;
-				} else {
-					disabled = false;
-				}
-				if (!host.UseSubMenusSingleFrame && host.UseKeysUpDownAsKeysLeftRight && barItems.SubMenu (barItems.Children [current]) != null &&
-					!disabled && host.IsMenuOpen) {
-					if (!CheckSubMenu ())
-						return false;
-					break;
-				}
-			} while (barItems.Children [current] == null || disabled);
-			SetNeedsDisplay ();
-			SetParentSetNeedsDisplay ();
-			if (!host.UseSubMenusSingleFrame)
-				host.OnMenuOpened ();
-			return true;
-		}
-
-		private void SetParentSetNeedsDisplay ()
-		{
-			if (host.openSubMenu != null) {
-				foreach (var menu in host.openSubMenu) {
-					menu.SetNeedsDisplay ();
-				}
-			}
-
-			host?.openMenu?.SetNeedsDisplay ();
-			host.SetNeedsDisplay ();
-		}
-
-		public override bool MouseEvent (MouseEvent me)
-		{
-			if (!host.handled && !host.HandleGrabView (me, this)) {
-				return false;
-			}
-			host.handled = false;
-			bool disabled;
-			var meY = me.Y - (Border == null ? 0 : Border.Thickness.Top);
-			if (me.Flags == MouseFlags.Button1Clicked) {
-				disabled = false;
-				if (meY < 0)
-					return true;
-				if (meY >= barItems.Children.Length)
-					return true;
-				var item = barItems.Children [meY];
-				if (item == null || !item.IsEnabled ()) disabled = true;
-				if (disabled) return true;
-				current = meY;
-				if (item != null && !disabled)
-					RunSelected ();
-				return true;
-			} else if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked ||
-				me.Flags == MouseFlags.Button1TripleClicked || me.Flags == MouseFlags.ReportMousePosition ||
-				me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) {
-
-				disabled = false;
-				if (meY < 0 || meY >= barItems.Children.Length) {
-					return true;
-				}
-				var item = barItems.Children [meY];
-				if (item == null) return true;
-				if (item == null || !item.IsEnabled ()) disabled = true;
-				if (item != null && !disabled)
-					current = meY;
-				if (host.UseSubMenusSingleFrame || !CheckSubMenu ()) {
-					SetNeedsDisplay ();
-					SetParentSetNeedsDisplay ();
-					return true;
-				}
-				host.OnMenuOpened ();
-				return true;
-			}
-			return false;
-		}
-
-		internal bool CheckSubMenu ()
-		{
-			if (current == -1 || barItems.Children [current] == null) {
-				return true;
-			}
-			var subMenu = barItems.SubMenu (barItems.Children [current]);
-			if (subMenu != null) {
-				int pos = -1;
-				if (host.openSubMenu != null) {
-					pos = host.openSubMenu.FindIndex (o => o?.barItems == subMenu);
-				}
-				if (pos == -1 && this != host.openCurrentMenu && subMenu.Children != host.openCurrentMenu.barItems.Children
-					&& !host.CloseMenu (false, true)) {
-					return false;
-				}
-				host.Activate (host.selected, pos, subMenu);
-			} else if (host.openSubMenu?.Count == 0 || host.openSubMenu?.Last ().barItems.IsSubMenuOf (barItems.Children [current]) == false) {
-				return host.CloseMenu (false, true);
-			} else {
-				SetNeedsDisplay ();
-				SetParentSetNeedsDisplay ();
-			}
-			return true;
-		}
-
-		int GetSubMenuIndex (MenuBarItem subMenu)
-		{
-			int pos = -1;
-			if (this != null && Subviews.Count > 0) {
-				Menu v = null;
-				foreach (var menu in Subviews) {
-					if (((Menu)menu).barItems == subMenu)
-						v = (Menu)menu;
-				}
-				if (v != null)
-					pos = Subviews.IndexOf (v);
-			}
-
-			return pos;
-		}
-
-		///<inheritdoc/>
-		public override bool OnEnter (View view)
-		{
-			Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
-
-			return base.OnEnter (view);
-		}
-
-		protected override void Dispose (bool disposing)
-		{
-			if (Application.Current != null) {
-				Application.Current.DrawContentComplete -= Current_DrawContentComplete;
-				Application.Current.SizeChanging -= Current_TerminalResized;
-			}
-			Application.MouseEvent -= Application_RootMouseEvent;
-			base.Dispose (disposing);
-		}
-	}
-
-	/// <summary>
-	///	<para>
-	/// Provides a menu bar that spans the top of a <see cref="Toplevel"/> View with drop-down and cascading menus. 
-	///	</para>
-	/// <para>
-	/// By default, any sub-sub-menus (sub-menus of the <see cref="MenuItem"/>s added to <see cref="MenuBarItem"/>s) 
-	/// are displayed in a cascading manner, where each sub-sub-menu pops out of the sub-menu frame
-	/// (either to the right or left, depending on where the sub-menu is relative to the edge of the screen). By setting
-	/// <see cref="UseSubMenusSingleFrame"/> to <see langword="true"/>, this behavior can be changed such that all sub-sub-menus are
-	/// drawn within a single frame below the MenuBar.
-	/// </para>
-	/// </summary>
-	/// <remarks>
-	///	<para>
-	///	The <see cref="MenuBar"/> appears on the first row of the parent <see cref="Toplevel"/> View and uses the full width.
-	///	</para>
-	///	<para>
-	///	The <see cref="MenuBar"/> provides global hotkeys for the application. See <see cref="MenuItem.HotKey"/>.
-	///	</para>
-	///	<para>
-	///	See also: <see cref="ContextMenu"/>
-	///	</para>
-	/// </remarks>
-	public class MenuBar : View {
-		internal int selected;
-		internal int selectedSub;
-
-		/// <summary>
-		/// Gets or sets the array of <see cref="MenuBarItem"/>s for the menu. Only set this after the <see cref="MenuBar"/> is visible.
-		/// </summary>
-		/// <value>The menu array.</value>
-		public MenuBarItem [] Menus { get; set; }
-
-		/// <summary>
-		/// The default <see cref="LineStyle"/> for <see cref="Menus"/>'s border. The default is <see cref="LineStyle.Single"/>.
-		/// </summary>
-		public LineStyle MenusBorderStyle { get; set; } = LineStyle.Single;
-
-		private bool useKeysUpDownAsKeysLeftRight = false;
-
-		/// <summary>
-		/// Used for change the navigation key style.
-		/// </summary>
-		public bool UseKeysUpDownAsKeysLeftRight {
-			get => useKeysUpDownAsKeysLeftRight;
-			set {
-				useKeysUpDownAsKeysLeftRight = value;
-				if (value && UseSubMenusSingleFrame) {
-					UseSubMenusSingleFrame = false;
-					SetNeedsDisplay ();
-				}
-			}
-		}
-
-		static string shortcutDelimiter = "+";
-		/// <summary>
-		/// Sets or gets the shortcut delimiter separator. The default is "+".
-		/// </summary>
-		public static string ShortcutDelimiter {
-			get => shortcutDelimiter;
-			set {
-				if (shortcutDelimiter != value) {
-					shortcutDelimiter = value == string.Empty ? " " : value;
-				}
-			}
-		}
-
-		/// <summary>
-		/// The specifier character for the hotkey to all menus.
-		/// </summary>
-		new public static Rune HotKeySpecifier => (Rune)'_';
-
-		private bool useSubMenusSingleFrame;
-
-		/// <summary>
-		/// Gets or sets if the sub-menus must be displayed in a single or multiple frames.
-		/// <para>
-		/// By default any sub-sub-menus (sub-menus of the main <see cref="MenuItem"/>s) are displayed in a cascading manner, 
-		/// where each sub-sub-menu pops out of the sub-menu frame
-		/// (either to the right or left, depending on where the sub-menu is relative to the edge of the screen). By setting
-		/// <see cref="UseSubMenusSingleFrame"/> to <see langword="true"/>, this behavior can be changed such that all sub-sub-menus are
-		/// drawn within a single frame below the MenuBar.
-		/// </para>		
-		/// </summary>
-		public bool UseSubMenusSingleFrame {
-			get => useSubMenusSingleFrame;
-			set {
-				useSubMenusSingleFrame = value;
-				if (value && UseKeysUpDownAsKeysLeftRight) {
-					useKeysUpDownAsKeysLeftRight = false;
-					SetNeedsDisplay ();
-				}
-			}
-		}
-
-		/// <summary>
-		/// The <see cref="Gui.Key"/> used to activate the menu bar by keyboard.
-		/// </summary>
-		public Key Key { get; set; } = Key.F9;
-
-		///<inheritdoc/>
-		public override bool Visible {
-			get => base.Visible;
-			set {
-				base.Visible = value;
-				if (!value) {
-					CloseAllMenus ();
-				}
-			}
-		}
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="MenuBar"/>.
-		/// </summary>
-		public MenuBar () : this (new MenuBarItem [] { }) { }
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="MenuBar"/> class with the specified set of Toplevel menu items.
-		/// </summary>
-		/// <param name="menus">Individual menu items; a null item will result in a separator being drawn.</param>
-		public MenuBar (MenuBarItem [] menus) : base ()
-		{
-			X = 0;
-			Y = 0;
-			Width = Dim.Fill ();
-			Height = 1;
-			Menus = menus;
-			//CanFocus = true;
-			selected = -1;
-			selectedSub = -1;
-			ColorScheme = Colors.Menu;
-			WantMousePositionReports = true;
-			IsMenuOpen = false;
-
-			Added += MenuBar_Added;
-
-			// Things this view knows how to do
-			AddCommand (Command.Left, () => { MoveLeft (); return true; });
-			AddCommand (Command.Right, () => { MoveRight (); return true; });
-			AddCommand (Command.Cancel, () => { CloseMenuBar (); return true; });
-			AddCommand (Command.Accept, () => { ProcessMenu (selected, Menus [selected]); return true; });
-
-			// Default keybindings for this view
-			AddKeyBinding (Key.CursorLeft, Command.Left);
-			AddKeyBinding (Key.CursorRight, Command.Right);
-			AddKeyBinding (Key.Esc, Command.Cancel);
-			AddKeyBinding (Key.C | Key.CtrlMask, Command.Cancel);
-			AddKeyBinding (Key.CursorDown, Command.Accept);
-			AddKeyBinding (Key.Enter, Command.Accept);
-		}
-
-		bool _initialCanFocus;
-
-		private void MenuBar_Added (object sender, SuperViewChangedEventArgs e)
-		{
-			_initialCanFocus = CanFocus;
-			Added -= MenuBar_Added;
-		}
-
-		bool openedByAltKey;
-
-		bool isCleaning;
-
-		///<inheritdoc/>
-		public override bool OnLeave (View view)
-		{
-			if ((!(view is MenuBar) && !(view is Menu) || !(view is MenuBar) && !(view is Menu) && openMenu != null) && !isCleaning && !reopen) {
-				CleanUp ();
-			}
-			return base.OnLeave (view);
-		}
-
-		///<inheritdoc/>
-		public override bool OnKeyDown (KeyEvent keyEvent)
-		{
-			if (keyEvent.IsAlt || (keyEvent.IsCtrl && keyEvent.Key == (Key.CtrlMask | Key.Space))) {
-				openedByAltKey = true;
-				SetNeedsDisplay ();
-				openedByHotKey = false;
-			}
-			return false;
-		}
-
-		///<inheritdoc/>
-		public override bool OnKeyUp (KeyEvent keyEvent)
-		{
-			if (keyEvent.IsAlt || keyEvent.Key == Key.AltMask || (keyEvent.IsCtrl && keyEvent.Key == (Key.CtrlMask | Key.Space))) {
-				// User pressed Alt - this may be a precursor to a menu accelerator (e.g. Alt-F)
-				if (openedByAltKey && !IsMenuOpen && openMenu == null && (((uint)keyEvent.Key & (uint)Key.CharMask) == 0
-					|| ((uint)keyEvent.Key & (uint)Key.CharMask) == (uint)Key.Space)) {
-					// There's no open menu, the first menu item should be highlight.
-					// The right way to do this is to SetFocus(MenuBar), but for some reason
-					// that faults.
-
-					var mbar = GetMouseGrabViewInstance (this);
-					if (mbar != null) {
-						mbar.CleanUp ();
-					}
-
-					//Activate (0);
-					//StartMenu ();
-					IsMenuOpen = true;
-					selected = 0;
-					CanFocus = true;
-					lastFocused = SuperView == null ? Application.Current.MostFocused : SuperView.MostFocused;
-					SetFocus ();
-					SetNeedsDisplay ();
-					Application.GrabMouse (this);
-				} else if (!openedByHotKey) {
-					// There's an open menu. If this Alt key-up is a pre-cursor to an accelerator
-					// we don't want to close the menu because it'll flash.
-					// How to deal with that?
-
-					CleanUp ();
-				}
-
-				return true;
-			}
-			return false;
-		}
-
-		internal void CleanUp ()
-		{
-			isCleaning = true;
-			if (openMenu != null) {
-				CloseAllMenus ();
-			}
-			openedByAltKey = false;
-			IsMenuOpen = false;
-			selected = -1;
-			CanFocus = _initialCanFocus;
-			if (lastFocused != null) {
-				lastFocused.SetFocus ();
-			}
-			SetNeedsDisplay ();
-			Application.UngrabMouse ();
-			isCleaning = false;
-		}
-
-		// The column where the MenuBar starts
-		static int xOrigin = 0;
-		// Spaces before the Title
-		static int leftPadding = 1;
-		// Spaces after the Title
-		static int rightPadding = 1;
-		// Spaces after the submenu Title, before Help
-		static int parensAroundHelp = 3;
-		///<inheritdoc/>
-		public override void OnDrawContent (Rect contentArea)
-		{
-			Move (0, 0);
-			Driver.SetAttribute (GetNormalColor ());
-			for (int i = 0; i < Frame.Width; i++)
-				Driver.AddRune ((Rune)' ');
-
-			Move (1, 0);
-			int pos = 0;
-
-			for (int i = 0; i < Menus.Length; i++) {
-				var menu = Menus [i];
-				Move (pos, 0);
-				Attribute hotColor, normalColor;
-				if (i == selected && IsMenuOpen) {
-					hotColor = i == selected ? ColorScheme.HotFocus : ColorScheme.HotNormal;
-					normalColor = i == selected ? ColorScheme.Focus : GetNormalColor ();
-				} else {
-					hotColor = ColorScheme.HotNormal;
-					normalColor = GetNormalColor ();
-				}
-				// Note Help on MenuBar is drawn with parens around it
-				DrawHotString (string.IsNullOrEmpty (menu.Help) ? $" {menu.Title} " : $" {menu.Title} ({menu.Help}) ", hotColor, normalColor);
-				pos += leftPadding + menu.TitleLength + (menu.Help.GetColumns () > 0 ? leftPadding + menu.Help.GetColumns () + parensAroundHelp : 0) + rightPadding;
-			}
-			PositionCursor ();
-		}
-
-		///<inheritdoc/>
-		public override void PositionCursor ()
-		{
-			if (selected == -1 && HasFocus && Menus.Length > 0) {
-				selected = 0;
-			}
-			int pos = 0;
-			for (int i = 0; i < Menus.Length; i++) {
-				if (i == selected) {
-					pos++;
-					Move (pos + 1, 0);
-					return;
-				} else {
-					pos += leftPadding + Menus [i].TitleLength + (Menus [i].Help.GetColumns () > 0 ? Menus [i].Help.GetColumns () + parensAroundHelp : 0) + rightPadding;
-				}
-			}
-		}
-
-		void Selected (MenuItem item)
-		{
-			var action = item.Action;
-
-			if (action == null)
-				return;
-
-			Application.UngrabMouse ();
-			CloseAllMenus ();
-			Application.Refresh ();
-
-			Run (action);
-		}
-
-		internal void Run (Action action)
-		{
-			Application.MainLoop.AddIdle (() => {
-				action ();
-				return false;
-			});
-		}
-
-		/// <summary>
-		/// Raised as a menu is opening.
-		/// </summary>
-		public event EventHandler<MenuOpeningEventArgs> MenuOpening;
-
-		/// <summary>
-		/// Raised when a menu is opened.
-		/// </summary>
-		public event EventHandler<MenuOpenedEventArgs> MenuOpened;
-
-		/// <summary>
-		/// Raised when a menu is closing passing <see cref="MenuClosingEventArgs"/>.
-		/// </summary>
-		public event EventHandler<MenuClosingEventArgs> MenuClosing;
-
-		/// <summary>
-		/// Raised when all the menu is closed.
-		/// </summary>
-		public event EventHandler MenuAllClosed;
-
-		// BUGBUG: Hack
-		internal Menu openMenu;
-		Menu ocm;
-		internal Menu openCurrentMenu {
-			get => ocm;
-			set {
-				if (ocm != value) {
-					ocm = value;
-					if (ocm != null && ocm.current > -1) {
-						OnMenuOpened ();
-					}
-				}
-			}
-		}
-		internal List<Menu> openSubMenu;
-		View previousFocused;
-		internal bool isMenuOpening;
-		internal bool isMenuClosing;
-
-		/// <summary>
-		/// <see langword="true"/> if the menu is open; otherwise <see langword="true"/>.
-		/// </summary>
-		public bool IsMenuOpen { get; protected set; }
-
-		/// <summary>
-		/// Virtual method that will invoke the <see cref="MenuOpening"/> event if it's defined.
-		/// </summary>
-		/// <param name="currentMenu">The current menu to be replaced.</param>
-		/// <returns>Returns the <see cref="MenuOpeningEventArgs"/></returns>
-		public virtual MenuOpeningEventArgs OnMenuOpening (MenuBarItem currentMenu)
-		{
-			var ev = new MenuOpeningEventArgs (currentMenu);
-			MenuOpening?.Invoke (this, ev);
-			return ev;
-		}
-
-		/// <summary>
-		/// Virtual method that will invoke the <see cref="MenuOpened"/> event if it's defined.
-		/// </summary>
-		public virtual void OnMenuOpened ()
-		{
-			MenuItem mi = null;
-			MenuBarItem parent;
-
-			if (openCurrentMenu.barItems.Children != null && openCurrentMenu.barItems.Children.Length > 0
-				&& openCurrentMenu?.current > -1) {
-				parent = openCurrentMenu.barItems;
-				mi = parent.Children [openCurrentMenu.current];
-			} else if (openCurrentMenu.barItems.IsTopLevel) {
-				parent = null;
-				mi = openCurrentMenu.barItems;
-			} else {
-				parent = openMenu.barItems;
-				mi = parent.Children [openMenu.current];
-			}
-			MenuOpened?.Invoke (this, new MenuOpenedEventArgs (parent, mi));
-		}
-
-		/// <summary>
-		/// Virtual method that will invoke the <see cref="MenuClosing"/>.
-		/// </summary>
-		/// <param name="currentMenu">The current menu to be closed.</param>
-		/// <param name="reopen">Whether the current menu will be reopen.</param>
-		/// <param name="isSubMenu">Whether is a sub-menu or not.</param>
-		public virtual MenuClosingEventArgs OnMenuClosing (MenuBarItem currentMenu, bool reopen, bool isSubMenu)
-		{
-			var ev = new MenuClosingEventArgs (currentMenu, reopen, isSubMenu);
-			MenuClosing?.Invoke (this, ev);
-			return ev;
-		}
-
-		/// <summary>
-		/// Virtual method that will invoke the <see cref="MenuAllClosed"/>.
-		/// </summary>
-		public virtual void OnMenuAllClosed ()
-		{
-			MenuAllClosed?.Invoke (this, EventArgs.Empty);
-		}
-
-		View lastFocused;
-
-		/// <summary>
-		/// Gets the view that was last focused before opening the menu.
-		/// </summary>
-		public View LastFocused { get; private set; }
-
-		internal void OpenMenu (int index, int sIndex = -1, MenuBarItem subMenu = null)
-		{
-			isMenuOpening = true;
-			var newMenu = OnMenuOpening (Menus [index]);
-			if (newMenu.Cancel) {
-				isMenuOpening = false;
-				return;
-			}
-			if (newMenu.NewMenuBarItem != null) {
-				Menus [index] = newMenu.NewMenuBarItem;
-			}
-			int pos = 0;
-			switch (subMenu) {
-			case null:
-				// Open a submenu below a MenuBar
-				lastFocused ??= (SuperView == null ? Application.Current.MostFocused : SuperView.MostFocused);
-				if (openSubMenu != null && !CloseMenu (false, true))
-					return;
-				if (openMenu != null) {
-					Application.Current.Remove (openMenu);
-					openMenu.Dispose ();
-					openMenu = null;
-				}
-
-				// This positions the submenu horizontally aligned with the first character of the
-				// text belonging to the menu 
-				for (int i = 0; i < index; i++)
-					pos += Menus [i].TitleLength + (Menus [i].Help.GetColumns () > 0 ? Menus [i].Help.GetColumns () + 2 : 0) + leftPadding + rightPadding;
-
-				var locationOffset = Point.Empty;
-				// if SuperView is null then it's from a ContextMenu
-				if (SuperView == null) {
-					locationOffset = GetScreenOffset ();
-				}
-				if (SuperView != null && SuperView != Application.Current) {
-					locationOffset.X += SuperView.Border.Thickness.Left;
-					locationOffset.Y += SuperView.Border.Thickness.Top;
-				}
-				openMenu = new Menu (this, Frame.X + pos + locationOffset.X, Frame.Y + 1 + locationOffset.Y, Menus [index], null, MenusBorderStyle);
-				openCurrentMenu = openMenu;
-				openCurrentMenu.previousSubFocused = openMenu;
-
-				Application.Current.Add (openMenu);
-				openMenu.SetFocus ();
-				break;
-			default:
-				// Opens a submenu next to another submenu (openSubMenu)
-				if (openSubMenu == null)
-					openSubMenu = new List<Menu> ();
-				if (sIndex > -1) {
-					RemoveSubMenu (sIndex);
-				} else {
-					var last = openSubMenu.Count > 0 ? openSubMenu.Last () : openMenu;
-					if (!UseSubMenusSingleFrame) {
-						locationOffset = GetLocationOffset ();
-						openCurrentMenu = new Menu (this, last.Frame.Left + last.Frame.Width + locationOffset.X, last.Frame.Top + locationOffset.Y + last.current, subMenu, last, MenusBorderStyle);
-					} else {
-						var first = openSubMenu.Count > 0 ? openSubMenu.First () : openMenu;
-						// 2 is for the parent and the separator
-						var mbi = new MenuItem [2 + subMenu.Children.Length];
-						mbi [0] = new MenuItem () { Title = subMenu.Title, Parent = subMenu };
-						mbi [1] = null;
-						for (int j = 0; j < subMenu.Children.Length; j++) {
-							mbi [j + 2] = subMenu.Children [j];
-						}
-						var newSubMenu = new MenuBarItem (mbi) { Parent = subMenu };
-						openCurrentMenu = new Menu (this, first.Frame.Left, first.Frame.Top, newSubMenu, null, MenusBorderStyle);
-						last.Visible = false;
-						Application.GrabMouse (openCurrentMenu);
-					}
-					openCurrentMenu.previousSubFocused = last.previousSubFocused;
-					openSubMenu.Add (openCurrentMenu);
-					Application.Current.Add (openCurrentMenu);
-				}
-				selectedSub = openSubMenu.Count - 1;
-				if (selectedSub > -1 && SelectEnabledItem (openCurrentMenu.barItems.Children, openCurrentMenu.current, out openCurrentMenu.current)) {
-					openCurrentMenu.SetFocus ();
-				}
-				break;
-			}
-			isMenuOpening = false;
-			IsMenuOpen = true;
-		}
-
-		Point GetLocationOffset ()
-		{
-			if (MenusBorderStyle != LineStyle.None) {
-				return new Point (0, 1);
-			}
-			return new Point (-2, 0);
-		}
-
-		/// <summary>
-		/// Opens the Menu programatically, as though the F9 key were pressed.
-		/// </summary>
-		public void OpenMenu ()
-		{
-			var mbar = GetMouseGrabViewInstance (this);
-			if (mbar != null) {
-				mbar.CleanUp ();
-			}
-
-			if (openMenu != null)
-				return;
-			selected = 0;
-			SetNeedsDisplay ();
-
-			previousFocused = SuperView == null ? Application.Current.Focused : SuperView.Focused;
-			OpenMenu (selected);
-			if (!SelectEnabledItem (openCurrentMenu.barItems.Children, openCurrentMenu.current, out openCurrentMenu.current) && !CloseMenu (false)) {
-				return;
-			}
-			if (!openCurrentMenu.CheckSubMenu ())
-				return;
-			Application.GrabMouse (this);
-		}
-
-		// Activates the menu, handles either first focus, or activating an entry when it was already active
-		// For mouse events.
-		internal void Activate (int idx, int sIdx = -1, MenuBarItem subMenu = null)
-		{
-			selected = idx;
-			selectedSub = sIdx;
-			if (openMenu == null)
-				previousFocused = SuperView == null ? Application.Current.Focused : SuperView.Focused;
-
-			OpenMenu (idx, sIdx, subMenu);
-			SetNeedsDisplay ();
-		}
-
-		internal bool SelectEnabledItem (IEnumerable<MenuItem> chldren, int current, out int newCurrent, bool forward = true)
-		{
-			if (chldren == null) {
-				newCurrent = -1;
-				return true;
-			}
-
-			IEnumerable<MenuItem> childrens;
-			if (forward) {
-				childrens = chldren;
-			} else {
-				childrens = chldren.Reverse ();
-			}
-			int count;
-			if (forward) {
-				count = -1;
-			} else {
-				count = childrens.Count ();
-			}
-			foreach (var child in childrens) {
-				if (forward) {
-					if (++count < current) {
-						continue;
-					}
-				} else {
-					if (--count > current) {
-						continue;
-					}
-				}
-				if (child == null || !child.IsEnabled ()) {
-					if (forward) {
-						current++;
-					} else {
-						current--;
-					}
-				} else {
-					newCurrent = current;
-					return true;
-				}
-			}
-			newCurrent = -1;
-			return false;
-		}
-
-		/// <summary>
-		/// Closes the Menu programmatically if open and not canceled (as though F9 were pressed).
-		/// </summary>
-		public bool CloseMenu (bool ignoreUseSubMenusSingleFrame = false)
-		{
-			return CloseMenu (false, false, ignoreUseSubMenusSingleFrame);
-		}
-
-		bool reopen;
-
-		internal bool CloseMenu (bool reopen = false, bool isSubMenu = false, bool ignoreUseSubMenusSingleFrame = false)
-		{
-			var mbi = isSubMenu ? openCurrentMenu.barItems : openMenu?.barItems;
-			if (UseSubMenusSingleFrame && mbi != null &&
-				!ignoreUseSubMenusSingleFrame && mbi.Parent != null) {
-				return false;
-			}
-			isMenuClosing = true;
-			this.reopen = reopen;
-			var args = OnMenuClosing (mbi, reopen, isSubMenu);
-			if (args.Cancel) {
-				isMenuClosing = false;
-				if (args.CurrentMenu.Parent != null)
-					openMenu.current = ((MenuBarItem)args.CurrentMenu.Parent).Children.IndexOf (args.CurrentMenu);
-				return false;
-			}
-			switch (isSubMenu) {
-			case false:
-				if (openMenu != null) {
-					Application.Current.Remove (openMenu);
-				}
-				SetNeedsDisplay ();
-				if (previousFocused != null && previousFocused is Menu && openMenu != null && previousFocused.ToString () != openCurrentMenu.ToString ())
-					previousFocused.SetFocus ();
-				openMenu?.Dispose ();
-				openMenu = null;
-				if (lastFocused is Menu || lastFocused is MenuBar) {
-					lastFocused = null;
-				}
-				LastFocused = lastFocused;
-				lastFocused = null;
-				if (LastFocused != null && LastFocused.CanFocus) {
-					if (!reopen) {
-						selected = -1;
-					}
-					if (openSubMenu != null) {
-						openSubMenu = null;
-					}
-					if (openCurrentMenu != null) {
-						Application.Current.Remove (openCurrentMenu);
-						openCurrentMenu.Dispose ();
-						openCurrentMenu = null;
-					}
-					LastFocused.SetFocus ();
-				} else if (openSubMenu == null || openSubMenu.Count == 0) {
-					CloseAllMenus ();
-				} else {
-					SetFocus ();
-					PositionCursor ();
-				}
-				IsMenuOpen = false;
-				break;
-
-			case true:
-				selectedSub = -1;
-				SetNeedsDisplay ();
-				RemoveAllOpensSubMenus ();
-				openCurrentMenu.previousSubFocused.SetFocus ();
-				openSubMenu = null;
-				IsMenuOpen = true;
-				break;
-			}
-			this.reopen = false;
-			isMenuClosing = false;
-			return true;
-		}
-
-		void RemoveSubMenu (int index, bool ignoreUseSubMenusSingleFrame = false)
-		{
-			if (openSubMenu == null || (UseSubMenusSingleFrame
-				&& !ignoreUseSubMenusSingleFrame && openSubMenu.Count == 0))
-
-				return;
-			for (int i = openSubMenu.Count - 1; i > index; i--) {
-				isMenuClosing = true;
-				Menu menu;
-				if (openSubMenu.Count - 1 > 0)
-					menu = openSubMenu [i - 1];
-				else
-					menu = openMenu;
-				if (!menu.Visible)
-					menu.Visible = true;
-				openCurrentMenu = menu;
-				openCurrentMenu.SetFocus ();
-				if (openSubMenu != null) {
-					menu = openSubMenu [i];
-					Application.Current.Remove (menu);
-					openSubMenu.Remove (menu);
-					menu.Dispose ();
-				}
-				RemoveSubMenu (i, ignoreUseSubMenusSingleFrame);
-			}
-			if (openSubMenu.Count > 0)
-				openCurrentMenu = openSubMenu.Last ();
-
-			isMenuClosing = false;
-		}
-
-		internal void RemoveAllOpensSubMenus ()
-		{
-			if (openSubMenu != null) {
-				foreach (var item in openSubMenu) {
-					Application.Current.Remove (item);
-					item.Dispose ();
-				}
-			}
-		}
-
-		internal void CloseAllMenus ()
-		{
-			if (!isMenuOpening && !isMenuClosing) {
-				if (openSubMenu != null && !CloseMenu (false, true, true))
-					return;
-				if (!CloseMenu (false))
-					return;
-				if (LastFocused != null && LastFocused != this)
-					selected = -1;
-				Application.UngrabMouse ();
-			}
-			IsMenuOpen = false;
-			openedByHotKey = false;
-			openedByAltKey = false;
-			OnMenuAllClosed ();
-		}
-
-		View FindDeepestMenu (View view, ref int count)
-		{
-			count = count > 0 ? count : 0;
-			foreach (var menu in view.Subviews) {
-				if (menu is Menu) {
-					count++;
-					return FindDeepestMenu ((Menu)menu, ref count);
-				}
-			}
-			return view;
-		}
-
-		internal void PreviousMenu (bool isSubMenu = false, bool ignoreUseSubMenusSingleFrame = false)
-		{
-			switch (isSubMenu) {
-			case false:
-				if (selected <= 0)
-					selected = Menus.Length - 1;
-				else
-					selected--;
-
-				if (selected > -1 && !CloseMenu (true, false, ignoreUseSubMenusSingleFrame))
-					return;
-				OpenMenu (selected);
-				if (!SelectEnabledItem (openCurrentMenu.barItems.Children, openCurrentMenu.current, out openCurrentMenu.current, false)) {
-					openCurrentMenu.current = 0;
-				}
-				break;
-			case true:
-				if (selectedSub > -1) {
-					selectedSub--;
-					RemoveSubMenu (selectedSub, ignoreUseSubMenusSingleFrame);
-					SetNeedsDisplay ();
-				} else
-					PreviousMenu ();
-
-				break;
-			}
-		}
-
-		internal void NextMenu (bool isSubMenu = false, bool ignoreUseSubMenusSingleFrame = false)
-		{
-			switch (isSubMenu) {
-			case false:
-				if (selected == -1)
-					selected = 0;
-				else if (selected + 1 == Menus.Length)
-					selected = 0;
-				else
-					selected++;
-
-				if (selected > -1 && !CloseMenu (true, ignoreUseSubMenusSingleFrame))
-					return;
-				OpenMenu (selected);
-				SelectEnabledItem (openCurrentMenu.barItems.Children, openCurrentMenu.current, out openCurrentMenu.current);
-				break;
-			case true:
-				if (UseKeysUpDownAsKeysLeftRight) {
-					if (CloseMenu (false, true, ignoreUseSubMenusSingleFrame)) {
-						NextMenu (false, ignoreUseSubMenusSingleFrame);
-					}
-				} else {
-					var subMenu = openCurrentMenu.current > -1 && openCurrentMenu.barItems.Children.Length > 0
-						? openCurrentMenu.barItems.SubMenu (openCurrentMenu.barItems.Children [openCurrentMenu.current])
-						: null;
-					if ((selectedSub == -1 || openSubMenu == null || openSubMenu?.Count - 1 == selectedSub) && subMenu == null) {
-						if (openSubMenu != null && !CloseMenu (false, true))
-							return;
-						NextMenu (false, ignoreUseSubMenusSingleFrame);
-					} else if (subMenu != null || (openCurrentMenu.current > -1
-						&& !openCurrentMenu.barItems.Children [openCurrentMenu.current].IsFromSubMenu)) {
-						selectedSub++;
-						openCurrentMenu.CheckSubMenu ();
-					} else {
-						if (CloseMenu (false, true, ignoreUseSubMenusSingleFrame)) {
-							NextMenu (false, ignoreUseSubMenusSingleFrame);
-						}
-						return;
-					}
-
-					SetNeedsDisplay ();
-					if (UseKeysUpDownAsKeysLeftRight)
-						openCurrentMenu.CheckSubMenu ();
-				}
-				break;
-			}
-		}
-
-		bool openedByHotKey;
-		internal bool FindAndOpenMenuByHotkey (KeyEvent kb)
-		{
-			//int pos = 0;
-			var c = ((uint)kb.Key & (uint)Key.CharMask);
-			for (int i = 0; i < Menus.Length; i++) {
-				// TODO: this code is duplicated, hotkey should be part of the MenuBarItem
-				var mi = Menus [i];
-				int p = mi.Title.IndexOf (MenuBar.HotKeySpecifier.ToString ());
-				if (p != -1 && p + 1 < mi.Title.GetRuneCount ()) {
-					if (Char.ToUpperInvariant ((char)mi.Title [p + 1]) == c) {
-						ProcessMenu (i, mi);
-						return true;
-					} else if (mi.Children?.Length > 0) {
-						if (FindAndOpenChildrenMenuByHotkey (kb, mi.Children)) {
-							return true;
-						}
-					}
-				} else if (mi.Children?.Length > 0) {
-					if (FindAndOpenChildrenMenuByHotkey (kb, mi.Children)) {
-						return true;
-					}
-				}
-			}
-
-			return false;
-		}
-
-		bool FindAndOpenChildrenMenuByHotkey (KeyEvent kb, MenuItem [] children)
-		{
-			var c = ((uint)kb.Key & (uint)Key.CharMask);
-			for (int i = 0; i < children.Length; i++) {
-				var mi = children [i];
-
-				if(mi == null) {
-					continue;
-				}
-
-				int p = mi.Title.IndexOf (MenuBar.HotKeySpecifier.ToString ());
-				if (p != -1 && p + 1 < mi.Title.GetRuneCount ()) {
-					if (Char.ToUpperInvariant ((char)mi.Title [p + 1]) == c) {
-						if (mi.IsEnabled ()) {
-							var action = mi.Action;
-							if (action != null) {
-								Run (action);
-							}
-						}
-						return true;
-					} else if (mi is MenuBarItem menuBarItem && menuBarItem?.Children.Length > 0) {
-						if (FindAndOpenChildrenMenuByHotkey (kb, menuBarItem.Children)) {
-							return true;
-						}
-					}
-				} else if (mi is MenuBarItem menuBarItem && menuBarItem?.Children.Length > 0) {
-					if (FindAndOpenChildrenMenuByHotkey (kb, menuBarItem.Children)) {
-						return true;
-					}
-				}
-			}
-			return false;
-		}
-
-		internal bool FindAndOpenMenuByShortcut (KeyEvent kb, MenuItem [] children = null)
-		{
-			if (children == null) {
-				children = Menus;
-			}
-
-			var key = kb.KeyValue;
-			var keys = ShortcutHelper.GetModifiersKey (kb);
-			key |= (int)keys;
-			for (int i = 0; i < children.Length; i++) {
-				var mi = children [i];
-				if (mi == null) {
-					continue;
-				}
-				if ((!(mi is MenuBarItem mbiTopLevel) || mbiTopLevel.IsTopLevel) && mi.Shortcut != Key.Null && mi.Shortcut == (Key)key) {
-					var action = mi.Action;
-					if (action != null) {
-						Application.MainLoop.AddIdle (() => {
-							action ();
-							return false;
-						});
-					}
-					return true;
-				}
-				if (mi is MenuBarItem menuBarItem && menuBarItem.Children != null && !menuBarItem.IsTopLevel && FindAndOpenMenuByShortcut (kb, menuBarItem.Children)) {
-					return true;
-				}
-			}
-
-			return false;
-		}
-
-		private void ProcessMenu (int i, MenuBarItem mi)
-		{
-			if (selected < 0 && IsMenuOpen) {
-				return;
-			}
-
-			if (mi.IsTopLevel) {
-				BoundsToScreen (i, 0, out int rx, out int ry);
-				var menu = new Menu (this, rx, ry, mi, null, MenusBorderStyle);
-				menu.Run (mi.Action);
-				menu.Dispose ();
-			} else {
-				openedByHotKey = true;
-				Application.GrabMouse (this);
-				selected = i;
-				OpenMenu (i);
-				if (!SelectEnabledItem (openCurrentMenu.barItems.Children, openCurrentMenu.current, out openCurrentMenu.current) && !CloseMenu (false)) {
-					return;
-				}
-				if (!openCurrentMenu.CheckSubMenu ())
-					return;
-			}
-			SetNeedsDisplay ();
-		}
-
-		///<inheritdoc/>
-		public override bool ProcessHotKey (KeyEvent kb)
-		{
-			if (kb.Key == Key) {
-				if (Visible && !IsMenuOpen) {
-					OpenMenu ();
-				} else {
-					CloseAllMenus ();
-				}
-				return true;
-			}
-
-			// To ncurses simulate a AltMask key pressing Alt+Space because
-			// it can't detect an alone special key down was pressed.
-			if (kb.IsAlt && kb.Key == Key.AltMask && openMenu == null) {
-				OnKeyDown (kb);
-				OnKeyUp (kb);
-				return true;
-			} else if (kb.IsAlt && !kb.IsCtrl && !kb.IsShift) {
-				if (FindAndOpenMenuByHotkey (kb)) return true;
-			}
-			//var kc = kb.KeyValue;
-
-			return base.ProcessHotKey (kb);
-		}
-
-		///<inheritdoc/>
-		public override bool ProcessKey (KeyEvent kb)
-		{
-			if (InvokeKeybindings (kb) == true)
-				return true;
-
-			var key = kb.KeyValue;
-			if ((key >= 'a' && key <= 'z') || (key >= 'A' && key <= 'Z') || (key >= '0' && key <= '9')) {
-				char c = Char.ToUpper ((char)key);
-
-				if (selected == -1 || Menus [selected].IsTopLevel)
-					return false;
-
-				foreach (var mi in Menus [selected].Children) {
-					if (mi == null)
-						continue;
-					int p = mi.Title.IndexOf (MenuBar.HotKeySpecifier.ToString ());
-					if (p != -1 && p + 1 < mi.Title.GetRuneCount ()) {
-						if (mi.Title [p + 1] == c) {
-							Selected (mi);
-							return true;
-						}
-					}
-				}
-			}
-
-			return false;
-		}
-
-		void CloseMenuBar ()
-		{
-			if (!CloseMenu (false))
-				return;
-			if (openedByAltKey) {
-				openedByAltKey = false;
-				LastFocused?.SetFocus ();
-			}
-			SetNeedsDisplay ();
-		}
-
-		void MoveRight ()
-		{
-			selected = (selected + 1) % Menus.Length;
-			OpenMenu (selected);
-			SetNeedsDisplay ();
-		}
-
-		void MoveLeft ()
-		{
-			selected--;
-			if (selected < 0)
-				selected = Menus.Length - 1;
-			OpenMenu (selected);
-			SetNeedsDisplay ();
-		}
-
-		///<inheritdoc/>
-		public override bool ProcessColdKey (KeyEvent kb)
-		{
-			return FindAndOpenMenuByShortcut (kb);
-		}
-
-		///<inheritdoc/>
-		public override bool MouseEvent (MouseEvent me)
-		{
-			if (!handled && !HandleGrabView (me, this)) {
-				return false;
-			}
-			handled = false;
-
-			if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked || me.Flags == MouseFlags.Button1TripleClicked || me.Flags == MouseFlags.Button1Clicked ||
-				(me.Flags == MouseFlags.ReportMousePosition && selected > -1) ||
-				(me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && selected > -1)) {
-				int pos = xOrigin;
-				Point locationOffset = default;
-				if (SuperView != null) {
-					locationOffset.X += SuperView.Border.Thickness.Left;
-					locationOffset.Y += SuperView.Border.Thickness.Top;
-				}
-				int cx = me.X - locationOffset.X;
-				for (int i = 0; i < Menus.Length; i++) {
-					if (cx >= pos && cx < pos + leftPadding + Menus [i].TitleLength + Menus [i].Help.GetColumns () + rightPadding) {
-						if (me.Flags == MouseFlags.Button1Clicked) {
-							if (Menus [i].IsTopLevel) {
-								BoundsToScreen (i, 0, out int rx, out int ry);
-								var menu = new Menu (this, rx, ry, Menus [i], null, MenusBorderStyle);
-								menu.Run (Menus [i].Action);
-								menu.Dispose ();
-							} else if (!IsMenuOpen) {
-								Activate (i);
-							}
-						} else if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked || me.Flags == MouseFlags.Button1TripleClicked) {
-							if (IsMenuOpen && !Menus [i].IsTopLevel) {
-								CloseAllMenus ();
-							} else if (!Menus [i].IsTopLevel) {
-								Activate (i);
-							}
-						} else if (selected != i && selected > -1 && (me.Flags == MouseFlags.ReportMousePosition ||
-							me.Flags == MouseFlags.Button1Pressed && me.Flags == MouseFlags.ReportMousePosition)) {
-							if (IsMenuOpen) {
-								if (!CloseMenu (true, false)) {
-									return true;
-								}
-								Activate (i);
-							}
-						} else if (IsMenuOpen) {
-							if (!UseSubMenusSingleFrame || (UseSubMenusSingleFrame && openCurrentMenu != null
-								&& openCurrentMenu.barItems.Parent != null && openCurrentMenu.barItems.Parent.Parent != Menus [i])) {
-
-								Activate (i);
-							}
-						}
-						return true;
-					} else if (i == Menus.Length - 1 && me.Flags == MouseFlags.Button1Clicked) {
-						if (IsMenuOpen && !Menus [i].IsTopLevel) {
-							CloseAllMenus ();
-							return true;
-						}
-					}
-					pos += leftPadding + Menus [i].TitleLength + rightPadding;
-				}
-			}
-			return false;
-		}
-
-		internal bool handled;
-		internal bool isContextMenuLoading;
-
-		internal bool HandleGrabView (MouseEvent me, View current)
-		{
-			if (Application.MouseGrabView != null) {
-				if (me.View is MenuBar || me.View is Menu) {
-					var mbar = GetMouseGrabViewInstance (me.View);
-					if (mbar != null) {
-						if (me.Flags == MouseFlags.Button1Clicked) {
-							mbar.CleanUp ();
-							Application.GrabMouse (me.View);
-						} else {
-							handled = false;
-							return false;
-						}
-					}
-					if (me.View != current) {
-						Application.UngrabMouse ();
-						var v = me.View;
-						Application.GrabMouse (v);
-						MouseEvent nme;
-						if (me.Y > -1) {
-							var newxy = v.ScreenToFrame (me.X, me.Y);
-							nme = new MouseEvent () {
-								X = newxy.X,
-								Y = newxy.Y,
-								Flags = me.Flags,
-								OfX = me.X - newxy.X,
-								OfY = me.Y - newxy.Y,
-								View = v
-							};
-						} else {
-							nme = new MouseEvent () {
-								X = me.X + current.Frame.X,
-								Y = 0,
-								Flags = me.Flags,
-								View = v
-							};
-						}
-
-						v.MouseEvent (nme);
-						return false;
-					}
-				} else if (!isContextMenuLoading && !(me.View is MenuBar || me.View is Menu)
-					&& me.Flags != MouseFlags.ReportMousePosition && me.Flags != 0) {
-
-					Application.UngrabMouse ();
-					if (IsMenuOpen)
-						CloseAllMenus ();
-					handled = false;
-					return false;
-				} else {
-					handled = false;
-					isContextMenuLoading = false;
-					return false;
-				}
-			} else if (!IsMenuOpen && (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked
-				|| me.Flags == MouseFlags.Button1TripleClicked || me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))) {
-
-				Application.GrabMouse (current);
-			} else if (IsMenuOpen && (me.View is MenuBar || me.View is Menu)) {
-				Application.GrabMouse (me.View);
-			} else {
-				handled = false;
-				return false;
-			}
-
-			handled = true;
-
-			return true;
-		}
-
-		MenuBar GetMouseGrabViewInstance (View view)
-		{
-			if (view == null || Application.MouseGrabView == null) {
-				return null;
-			}
-
-			MenuBar hostView = null;
-			if (view is MenuBar) {
-				hostView = (MenuBar)view;
-			} else if (view is Menu) {
-				hostView = ((Menu)view).host;
-			}
-
-			var grabView = Application.MouseGrabView;
-			MenuBar hostGrabView = null;
-			if (grabView is MenuBar) {
-				hostGrabView = (MenuBar)grabView;
-			} else if (grabView is Menu) {
-				hostGrabView = ((Menu)grabView).host;
-			}
-
-			return hostView != hostGrabView ? hostGrabView : null;
-		}
-
-		///<inheritdoc/>
-		public override bool OnEnter (View view)
-		{
-			Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
-
-			return base.OnEnter (view);
-		}
-
-		/// <summary>
-		/// Gets the superview location offset relative to the <see cref="ConsoleDriver"/> location.
-		/// </summary>
-		/// <returns>The location offset.</returns>
-		internal Point GetScreenOffset ()
-		{
-			var superViewFrame = SuperView == null ? new Rect (0, 0, Driver.Cols, Driver.Rows) : SuperView.Frame;
-			var sv = SuperView == null ? Application.Current : SuperView;
-			var boundsOffset = sv.GetBoundsOffset ();
-			return new Point (superViewFrame.X - sv.Frame.X - boundsOffset.X,
-				superViewFrame.Y - sv.Frame.Y - boundsOffset.Y);
-		}
-
-		/// <summary>
-		/// Gets the <see cref="Application.Current"/> location offset relative to the <see cref="ConsoleDriver"/> location.
-		/// </summary>
-		/// <returns>The location offset.</returns>
-		internal Point GetScreenOffsetFromCurrent ()
-		{
-			var screen = new Rect (0, 0, Driver.Cols, Driver.Rows);
-			var currentFrame = Application.Current.Frame;
-			var boundsOffset = Application.Top.GetBoundsOffset ();
-			return new Point (screen.X - currentFrame.X - boundsOffset.X
-				, screen.Y - currentFrame.Y - boundsOffset.Y);
-		}
-	}
-}

+ 242 - 0
Terminal.Gui/Views/Menu/ContextMenu.cs

@@ -0,0 +1,242 @@
+using System;
+
+namespace Terminal.Gui;
+
+/// <summary>
+/// ContextMenu provides a pop-up menu that can be positioned anywhere within a <see cref="View"/>. 
+/// ContextMenu is analogous to <see cref="MenuBar"/> and, once activated, works like a sub-menu 
+/// of a <see cref="MenuBarItem"/> (but can be positioned anywhere).
+/// <para>
+/// By default, a ContextMenu with sub-menus is displayed in a cascading manner, where each sub-menu pops out of the ContextMenu frame
+/// (either to the right or left, depending on where the ContextMenu is relative to the edge of the screen). By setting
+/// <see cref="UseSubMenusSingleFrame"/> to <see langword="true"/>, this behavior can be changed such that all sub-menus are
+/// drawn within the ContextMenu frame.
+/// </para>
+/// <para>
+/// ContextMenus can be activated using the Shift-F10 key (by default; use the <see cref="Key"/> to change to another key).
+/// </para>
+/// <para>
+/// Callers can cause the ContextMenu to be activated on a right-mouse click (or other interaction) by calling <see cref="Show()"/>.
+/// </para>
+/// <para>
+/// ContextMenus are located using screen using screen coordinates and appear above all other Views.
+/// </para>
+/// </summary>
+public sealed class ContextMenu : IDisposable {
+	/// <summary>
+	/// The default shortcut key for activating the context menu.
+	/// </summary>
+	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+	public static Key DefaultKey { get; set; } = Key.F10.WithShift;
+
+	static MenuBar _menuBar;
+	Key _key = DefaultKey;
+	MouseFlags _mouseFlags = MouseFlags.Button3Clicked;
+	Toplevel _container;
+
+	/// <summary>
+	/// Initializes a context menu with no menu items.
+	/// </summary>
+	public ContextMenu () : this (0, 0, new MenuBarItem ()) { }
+
+	/// <summary>
+	/// Initializes a context menu, with a <see cref="View"/> specifying the parent/host of the menu.
+	/// </summary>
+	/// <param name="host">The host view.</param>
+	/// <param name="menuItems">The menu items for the context menu.</param>
+	public ContextMenu (View host, MenuBarItem menuItems) :
+		this (host.Frame.X, host.Frame.Y, menuItems)
+	{
+		Host = host;
+	}
+
+	/// <summary>
+	/// Initializes a context menu with menu items at a specific screen location.
+	/// </summary>
+	/// <param name="x">The left position (screen relative).</param>
+	/// <param name="y">The top position (screen relative).</param>
+	/// <param name="menuItems">The menu items.</param>
+	public ContextMenu (int x, int y, MenuBarItem menuItems)
+	{
+		if (IsShow) {
+			if (_menuBar.SuperView != null) {
+				Hide ();
+			}
+			IsShow = false;
+		}
+		MenuItems = menuItems;
+		Position = new Point (x, y);
+	}
+
+	void MenuBar_MenuAllClosed (object sender, EventArgs e)
+	{
+		Dispose ();
+	}
+
+	/// <summary>
+	/// Disposes the context menu object.
+	/// </summary>
+	public void Dispose ()
+	{
+		if (IsShow) {
+			_menuBar.MenuAllClosed -= MenuBar_MenuAllClosed;
+			_menuBar.Dispose ();
+			_menuBar = null;
+			IsShow = false;
+		}
+		if (_container != null) {
+			_container.Closing -= Container_Closing;
+		}
+	}
+
+	/// <summary>
+	/// Shows (opens) the ContextMenu, displaying the <see cref="MenuItem"/>s it contains.
+	/// </summary>
+	public void Show ()
+	{
+		if (_menuBar != null) {
+			Hide ();
+		}
+		_container = Application.Current;
+		_container.Closing += Container_Closing;
+		var frame = new Rect (0, 0, View.Driver.Cols, View.Driver.Rows);
+		var position = Position;
+		if (Host != null) {
+			Host.BoundsToScreen (frame.X, frame.Y, out int x, out int y);
+			var pos = new Point (x, y);
+			pos.Y += Host.Frame.Height - 1;
+			if (position != pos) {
+				Position = position = pos;
+			}
+		}
+		var rect = Menu.MakeFrame (position.X, position.Y, MenuItems.Children);
+		if (rect.Right >= frame.Right) {
+			if (frame.Right - rect.Width >= 0 || !ForceMinimumPosToZero) {
+				position.X = frame.Right - rect.Width;
+			} else if (ForceMinimumPosToZero) {
+				position.X = 0;
+			}
+		} else if (ForceMinimumPosToZero && position.X < 0) {
+			position.X = 0;
+		}
+		if (rect.Bottom >= frame.Bottom) {
+			if (frame.Bottom - rect.Height - 1 >= 0 || !ForceMinimumPosToZero) {
+				if (Host == null) {
+					position.Y = frame.Bottom - rect.Height - 1;
+				} else {
+					Host.BoundsToScreen (frame.X, frame.Y, out int x, out int y);
+					var pos = new Point (x, y);
+					position.Y = pos.Y - rect.Height - 1;
+				}
+			} else if (ForceMinimumPosToZero) {
+				position.Y = 0;
+			}
+		} else if (ForceMinimumPosToZero && position.Y < 0) {
+			position.Y = 0;
+		}
+
+		_menuBar = new MenuBar (new [] { MenuItems }) {
+			X = position.X,
+			Y = position.Y,
+			Width = 0,
+			Height = 0,
+			UseSubMenusSingleFrame = UseSubMenusSingleFrame,
+			Key = Key
+		};
+
+		_menuBar._isContextMenuLoading = true;
+		_menuBar.MenuAllClosed += MenuBar_MenuAllClosed;
+		_menuBar.BeginInit ();
+		_menuBar.EndInit ();
+		IsShow = true;
+		_menuBar.OpenMenu ();
+	}
+
+	void Container_Closing (object sender, ToplevelClosingEventArgs obj)
+	{
+		Hide ();
+	}
+
+	/// <summary>
+	/// Hides (closes) the ContextMenu.
+	/// </summary>
+	public void Hide ()
+	{
+		_menuBar?.CleanUp ();
+		Dispose ();
+	}
+
+	/// <summary>
+	/// Event invoked when the <see cref="ContextMenu.Key"/> is changed.
+	/// </summary>
+	public event EventHandler<KeyChangedEventArgs> KeyChanged;
+
+	/// <summary>
+	/// Event invoked when the <see cref="ContextMenu.MouseFlags"/> is changed.
+	/// </summary>
+	public event EventHandler<MouseFlagsChangedEventArgs> MouseFlagsChanged;
+
+	/// <summary>
+	/// Gets or sets the menu position.
+	/// </summary>
+	public Point Position { get; set; }
+
+	/// <summary>
+	/// Gets or sets the menu items for this context menu.
+	/// </summary>
+	public MenuBarItem MenuItems { get; set; }
+
+	/// <summary>
+	/// Specifies the key that will activate the context menu.
+	/// </summary>
+	public Key Key {
+		get => _key;
+		set {
+			var oldKey = _key;
+			_key = value;
+			KeyChanged?.Invoke (this, new KeyChangedEventArgs (oldKey, _key));
+		}
+	}
+
+	/// <summary>
+	/// <see cref="Gui.MouseFlags"/> specifies the mouse action used to activate the context menu by mouse.
+	/// </summary>
+	public MouseFlags MouseFlags {
+		get => _mouseFlags;
+		set {
+			var oldFlags = _mouseFlags;
+			_mouseFlags = value;
+			MouseFlagsChanged?.Invoke (this, new MouseFlagsChangedEventArgs (oldFlags, value));
+		}
+	}
+
+	/// <summary>
+	/// Gets whether the ContextMenu is showing or not.
+	/// </summary>
+	public static bool IsShow { get; private set; }
+
+	/// <summary>
+	/// The host <see cref="View "/> which position will be used,
+	/// otherwise if it's null the container will be used.
+	/// </summary>
+	public View Host { get; set; }
+
+	/// <summary>
+	/// Sets or gets whether the context menu be forced to the right, ensuring it is not clipped, if the x position 
+	/// is less than zero. The default is <see langword="true"/> which means the context menu will be forced to the right.
+	/// If set to <see langword="false"/>, the context menu will be clipped on the left if x is less than zero.
+	/// </summary>
+	public bool ForceMinimumPosToZero { get; set; } = true;
+
+	/// <summary>
+	/// Gets the <see cref="MenuBar"/> that is hosting this context menu.
+	/// </summary>
+	public MenuBar MenuBar => _menuBar;
+
+	/// <summary>
+	/// Gets or sets if sub-menus will be displayed using a "single frame" menu style. If <see langword="true"/>, the ContextMenu
+	/// and any sub-menus that would normally cascade will be displayed within a single frame. If <see langword="false"/> (the default),
+	/// sub-menus will cascade using separate frames for each level of the menu hierarchy.
+	/// </summary>
+	public bool UseSubMenusSingleFrame { get; set; }
+}

+ 1029 - 0
Terminal.Gui/Views/Menu/Menu.cs

@@ -0,0 +1,1029 @@
+using System;
+using System.Diagnostics;
+using System.Text;
+using System.Linq;
+using System.Reflection.Metadata;
+
+namespace Terminal.Gui;
+
+/// <summary>
+/// Specifies how a <see cref="MenuItem"/> shows selection state. 
+/// </summary>
+[Flags]
+public enum MenuItemCheckStyle {
+	/// <summary>
+	/// The menu item will be shown normally, with no check indicator. The default.
+	/// </summary>
+	NoCheck = 0b_0000_0000,
+
+	/// <summary>
+	/// The menu item will indicate checked/un-checked state (see <see cref="Checked"/>).
+	/// </summary>
+	Checked = 0b_0000_0001,
+
+	/// <summary>
+	/// The menu item is part of a menu radio group (see <see cref="Checked"/>) and will indicate selected state.
+	/// </summary>
+	Radio = 0b_0000_0010
+};
+
+/// <summary>
+/// A <see cref="MenuItem"/> has title, an associated help text, and an action to execute on activation. 
+/// MenuItems can also have a checked indicator (see <see cref="Checked"/>).
+/// </summary>
+public class MenuItem {
+	string _title;
+	ShortcutHelper _shortcutHelper;
+	bool _allowNullChecked;
+	MenuItemCheckStyle _checkType;
+
+	internal int TitleLength => GetMenuBarItemLength (Title);
+
+	/// <summary>
+	/// Gets or sets arbitrary data for the menu item.
+	/// </summary>
+	/// <remarks>This property is not used internally.</remarks>
+	public object Data { get; set; }
+
+	// TODO: Update to use Key instead of KeyCode
+	/// <summary>
+	/// Initializes a new instance of <see cref="MenuItem"/>
+	/// </summary>
+	public MenuItem (KeyCode shortcut = KeyCode.Null) : this ("", "", null, null, null, shortcut) { }
+
+	// TODO: Update to use Key instead of KeyCode
+	/// <summary>
+	/// Initializes a new instance of <see cref="MenuItem"/>.
+	/// </summary>
+	/// <param name="title">Title for the menu item.</param>
+	/// <param name="help">Help text to display.</param>
+	/// <param name="action">Action to invoke when the menu item is activated.</param>
+	/// <param name="canExecute">Function to determine if the action can currently be executed.</param>
+	/// <param name="parent">The <see cref="Parent"/> of this menu item.</param>
+	/// <param name="shortcut">The <see cref="Shortcut"/> keystroke combination.</param>
+	public MenuItem (string title, string help, Action action, Func<bool> canExecute = null, MenuItem parent = null, KeyCode shortcut = KeyCode.Null)
+	{
+		Title = title ?? "";
+		Help = help ?? "";
+		Action = action;
+		CanExecute = canExecute;
+		Parent = parent;
+		_shortcutHelper = new ShortcutHelper ();
+		if (shortcut != KeyCode.Null) {
+			Shortcut = shortcut;
+		}
+	}
+
+	#region Keyboard Handling
+
+	// TODO: Update to use Key instead of Rune
+	/// <summary>
+	/// The HotKey is used to activate a <see cref="MenuItem"/> with the keyboard. HotKeys are defined by prefixing the <see cref="Title"/>
+	/// of a MenuItem with an underscore ('_'). 
+	/// <para>
+	/// Pressing Alt-Hotkey for a <see cref="MenuBarItem"/> (menu items on the menu bar) works even if the menu is not active). 
+	/// Once a menu has focus and is active, pressing just the HotKey will activate the MenuItem.
+	/// </para>
+	/// <para>
+	/// For example for a MenuBar with a "_File" MenuBarItem that contains a "_New" MenuItem, Alt-F will open the File menu.
+	/// Pressing the N key will then activate the New MenuItem.
+	/// </para>
+	/// <para>
+	/// See also <see cref="Shortcut"/> which enable global key-bindings to menu items.
+	/// </para>
+	/// </summary>
+	public Rune HotKey { get; set; }
+
+	void GetHotKey ()
+	{
+		bool nextIsHot = false;
+		foreach (char x in _title) {
+			if (x == MenuBar.HotKeySpecifier.Value) {
+				nextIsHot = true;
+			} else {
+				if (nextIsHot) {
+					HotKey = (Rune)char.ToUpper (x);
+					break;
+				}
+				nextIsHot = false;
+				HotKey = default;
+			}
+		}
+	}
+
+
+	// TODO: Update to use Key instead of KeyCode
+	/// <summary>
+	/// Shortcut defines a key binding to the MenuItem that will invoke the MenuItem's action globally for the <see cref="View"/> that is
+	/// the parent of the <see cref="MenuBar"/> or <see cref="ContextMenu"/> this <see cref="MenuItem"/>.
+	/// <para>
+	/// The <see cref="KeyCode"/> will be drawn on the MenuItem to the right of the <see cref="Title"/> and <see cref="Help"/> text. See <see cref="ShortcutTag"/>.
+	/// </para>
+	/// </summary>
+	public KeyCode Shortcut {
+		get => _shortcutHelper.Shortcut;
+		set {
+
+			if (_shortcutHelper.Shortcut != value && (ShortcutHelper.PostShortcutValidation (value) || value == KeyCode.Null)) {
+				_shortcutHelper.Shortcut = value;
+			}
+		}
+	}
+
+	/// <summary>
+	/// Gets the text describing the keystroke combination defined by <see cref="Shortcut"/>.
+	/// </summary>
+	public string ShortcutTag => Key.ToString (_shortcutHelper.Shortcut, MenuBar.ShortcutDelimiter);
+	#endregion Keyboard Handling
+
+	/// <summary>
+	/// Gets or sets the title of the menu item .
+	/// </summary>
+	/// <value>The title.</value>
+	public string Title {
+		get => _title;
+		set {
+			if (_title != value) {
+				_title = value;
+				GetHotKey ();
+			}
+		}
+	}
+
+	/// <summary>
+	/// Gets or sets the help text for the menu item. The help text is drawn to the right of the <see cref="Title"/>.
+	/// </summary>
+	/// <value>The help text.</value>
+	public string Help { get; set; }
+
+	/// <summary>
+	/// Gets or sets the action to be invoked when the menu item is triggered.
+	/// </summary>
+	/// <value>Method to invoke.</value>
+	public Action Action { get; set; }
+
+	/// <summary>
+	/// Gets or sets the action to be invoked to determine if the menu can be triggered. If <see cref="CanExecute"/> returns <see langword="true"/>
+	/// the menu item will be enabled. Otherwise, it will be disabled. 
+	/// </summary>
+	/// <value>Function to determine if the action is can be executed or not.</value>
+	public Func<bool> CanExecute { get; set; }
+
+	/// <summary>
+	/// Returns <see langword="true"/> if the menu item is enabled. This method is a wrapper around <see cref="CanExecute"/>.
+	/// </summary>
+	public bool IsEnabled ()
+	{
+		return CanExecute == null ? true : CanExecute ();
+	}
+
+	// 
+	// ┌─────────────────────────────┐
+	// │ Quit  Quit UI Catalog  Ctrl+Q │
+	// └─────────────────────────────┘
+	// ┌─────────────────┐
+	// │ ◌ TopLevel Alt+T │
+	// └─────────────────┘
+	// TODO: Replace the `2` literals with named constants 
+	internal int Width => 1 + // space before Title
+				TitleLength +
+				2 + // space after Title - BUGBUG: This should be 1 
+				(Checked == true || CheckType.HasFlag (MenuItemCheckStyle.Checked) || CheckType.HasFlag (MenuItemCheckStyle.Radio) ? 2 : 0) + // check glyph + space 
+				(Help.GetColumns () > 0 ? 2 + Help.GetColumns () : 0) + // Two spaces before Help
+				(ShortcutTag.GetColumns () > 0 ? 2 + ShortcutTag.GetColumns () : 0); // Pad two spaces before shortcut tag (which are also aligned right)
+
+	/// <summary>
+	/// Sets or gets whether the <see cref="MenuItem"/> shows a check indicator or not. See <see cref="MenuItemCheckStyle"/>.
+	/// </summary>
+	public bool? Checked { set; get; }
+
+	/// <summary>
+	/// Used only if <see cref="CheckType"/> is of <see cref="MenuItemCheckStyle.Checked"/> type.
+	/// If <see langword="true"/> allows <see cref="Checked"/> to be null, true or false.
+	/// If <see langword="false"/> only allows <see cref="Checked"/> to be true or false.
+	/// </summary>
+	public bool AllowNullChecked {
+		get => _allowNullChecked;
+		set {
+			_allowNullChecked = value;
+			if (Checked == null) {
+				Checked = false;
+			}
+		}
+	}
+
+	/// <summary>
+	/// Sets or gets the <see cref="MenuItemCheckStyle"/> of a menu item where <see cref="Checked"/> is set to <see langword="true"/>.
+	/// </summary>
+	public MenuItemCheckStyle CheckType {
+		get => _checkType;
+		set {
+			_checkType = value;
+			if (_checkType == MenuItemCheckStyle.Checked && !_allowNullChecked && Checked == null) {
+				Checked = false;
+			}
+		}
+	}
+
+	/// <summary>
+	/// Gets the parent for this <see cref="MenuItem"/>.
+	/// </summary>
+	/// <value>The parent.</value>
+	public MenuItem Parent { get; set; }
+
+	/// <summary>
+	/// Gets if this <see cref="MenuItem"/> is from a sub-menu.
+	/// </summary>
+	internal bool IsFromSubMenu => Parent != null;
+
+	/// <summary>
+	/// Merely a debugging aid to see the interaction with main.
+	/// </summary>
+	public MenuItem GetMenuItem ()
+	{
+		return this;
+	}
+
+	/// <summary>
+	/// Merely a debugging aid to see the interaction with main.
+	/// </summary>
+	public bool GetMenuBarItem ()
+	{
+		return IsFromSubMenu;
+	}
+
+	/// <summary>
+	/// Toggle the <see cref="Checked"/> between three states if <see cref="AllowNullChecked"/> is <see langword="true"/>
+	/// or between two states if <see cref="AllowNullChecked"/> is <see langword="false"/>.
+	/// </summary>
+	public void ToggleChecked ()
+	{
+		if (_checkType != MenuItemCheckStyle.Checked) {
+			throw new InvalidOperationException ("This isn't a Checked MenuItemCheckStyle!");
+		}
+		bool? previousChecked = Checked;
+		if (AllowNullChecked) {
+			switch (previousChecked) {
+				case null:
+					Checked = true;
+					break;
+				case true:
+					Checked = false;
+					break;
+				case false:
+					Checked = null;
+					break;
+			}
+		} else {
+			Checked = !Checked;
+		}
+	}
+
+
+	int GetMenuBarItemLength (string title)
+	{
+		int len = 0;
+		foreach (var ch in title.EnumerateRunes ()) {
+			if (ch == MenuBar.HotKeySpecifier) {
+				continue;
+			}
+			len += Math.Max (ch.GetColumns (), 1);
+		}
+
+		return len;
+	}
+}
+
+/// <summary>
+/// An internal class used to represent a menu pop-up menu. Created and managed by <see cref="MenuBar"/> and <see cref="ContextMenu"/>.
+/// </summary>
+class Menu : View {
+	internal MenuBarItem _barItems;
+	internal MenuBar _host;
+	internal int _currentChild;
+	internal View _previousSubFocused;
+
+	internal static Rect MakeFrame (int x, int y, MenuItem [] items, Menu parent = null, LineStyle border = LineStyle.Single)
+	{
+		if (items == null || items.Length == 0) {
+			return new Rect ();
+		}
+		int minX = x;
+		int minY = y;
+		int borderOffset = 2; // This 2 is for the space around
+		int maxW = (items.Max (z => z?.Width) ?? 0) + borderOffset;
+		int maxH = items.Length + borderOffset;
+		if (parent != null && x + maxW > Driver.Cols) {
+			minX = Math.Max (parent.Frame.Right - parent.Frame.Width - maxW, 0);
+		}
+		if (y + maxH > Driver.Rows) {
+			minY = Math.Max (Driver.Rows - maxH, 0);
+		}
+		return new Rect (minX, minY, maxW, maxH);
+	}
+
+	public Menu (MenuBar host, int x, int y, MenuBarItem barItems, Menu parent = null, LineStyle border = LineStyle.Single)
+		: base (MakeFrame (x, y, barItems?.Children, parent, border))
+	{
+		if (host == null) {
+			throw new ArgumentNullException (nameof (host));
+		}
+
+		if (barItems == null) {
+			throw new ArgumentNullException (nameof (barItems));
+		} 
+		
+		_host = host;
+		_barItems = barItems;
+
+		if (barItems is { IsTopLevel: true }) {
+			// This is a standalone MenuItem on a MenuBar
+			ColorScheme = host.ColorScheme;
+			CanFocus = true;
+		} else {
+
+			_currentChild = -1;
+			for (int i = 0; i < barItems.Children?.Length; i++) {
+				if (barItems.Children [i]?.IsEnabled () == true) {
+					_currentChild = i;
+					break;
+				}
+
+			}
+			ColorScheme = host.ColorScheme;
+			CanFocus = true;
+			WantMousePositionReports = host.WantMousePositionReports;
+		}
+
+		BorderStyle = host.MenusBorderStyle;
+
+		if (Application.Current != null) {
+			Application.Current.DrawContentComplete += Current_DrawContentComplete;
+			Application.Current.SizeChanging += Current_TerminalResized;
+		}
+		Application.MouseEvent += Application_RootMouseEvent;
+
+		// Things this view knows how to do
+		AddCommand (Command.LineUp, () => MoveUp ());
+		AddCommand (Command.LineDown, () => MoveDown ());
+		AddCommand (Command.Left, () => {
+			_host.PreviousMenu (true);
+			return true;
+		});
+		AddCommand (Command.Right, () => {
+			_host.NextMenu (!_barItems.IsTopLevel || _barItems.Children != null
+				&& _barItems.Children.Length > 0 && _currentChild > -1
+				&& _currentChild < _barItems.Children.Length && _barItems.Children [_currentChild].IsFromSubMenu,
+				_barItems.Children != null && _barItems.Children.Length > 0 && _currentChild > -1
+				&& host.UseSubMenusSingleFrame && _barItems.SubMenu (_barItems.Children [_currentChild]) != null);
+
+			return true;
+		});
+		AddCommand (Command.Cancel, () => {
+			CloseAllMenus ();
+			return true;
+		});
+		AddCommand (Command.Accept, () => {
+			RunSelected ();
+			return true;
+		});
+		AddCommand (Command.Select, () => _host?.SelectItem (_menuItemToSelect));
+		AddCommand (Command.ToggleExpandCollapse, () => SelectOrRun ());
+		AddCommand (Command.Default, () => _host?.SelectItem (_menuItemToSelect));
+
+		// Default key bindings for this view
+		KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
+		KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
+		KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
+		KeyBindings.Add (KeyCode.CursorRight, Command.Right);
+		KeyBindings.Add (KeyCode.Esc, Command.Cancel);
+		KeyBindings.Add (KeyCode.Enter, Command.Accept);
+		KeyBindings.Add (KeyCode.F9, KeyBindingScope.HotKey, Command.ToggleExpandCollapse);
+		KeyBindings.Add (KeyCode.CtrlMask | KeyCode.Space, KeyBindingScope.HotKey, Command.ToggleExpandCollapse);
+
+		AddKeyBindings (barItems);
+#if SUPPORT_ALT_TO_ACTIVATE_MENU
+		Initialized += (s, e) => {
+			if (SuperView != null) {
+				SuperView.KeyUp += SuperView_KeyUp;
+			}
+		};
+#endif
+		// Debugging aid so ToString() is helpful
+		Text = _barItems.Title;
+	}
+
+
+#if SUPPORT_ALT_TO_ACTIVATE_MENU
+	void SuperView_KeyUp (object sender, KeyEventArgs e)
+	{
+		if (SuperView == null || SuperView.CanFocus == false || SuperView.Visible == false) {
+			return;
+		}
+		_host.AltKeyUpHandler (e);
+	}
+#endif
+
+	void AddKeyBindings (MenuBarItem menuBarItem)
+	{
+		if (menuBarItem == null || menuBarItem.Children == null) {
+			return;
+		}
+		foreach (var menuItem in menuBarItem.Children.Where (m => m != null)) {
+			KeyBindings.Add ((KeyCode)menuItem.HotKey.Value, Command.ToggleExpandCollapse);
+			KeyBindings.Add ((KeyCode)menuItem.HotKey.Value | KeyCode.AltMask, Command.ToggleExpandCollapse);
+			if (menuItem.Shortcut != KeyCode.Unknown) {
+				KeyBindings.Add (menuItem.Shortcut, KeyBindingScope.HotKey, Command.Select);
+			}
+			var subMenu = menuBarItem.SubMenu (menuItem);
+			AddKeyBindings (subMenu);
+		}
+	}
+
+	int _menuBarItemToActivate = -1;
+	MenuItem _menuItemToSelect;
+
+	/// <summary>
+	/// Called when a key bound to Command.Select is pressed. This means a hot key was pressed.
+	/// </summary>
+	/// <returns></returns>
+	bool SelectOrRun ()
+	{
+		if (!IsInitialized || !Visible) {
+			return true;
+		}
+
+		if (_menuBarItemToActivate != -1) {
+			_host.Activate (1, _menuBarItemToActivate);
+		} else if (_menuItemToSelect != null) {
+			var m = _menuItemToSelect as MenuBarItem;
+			if (m?.Children?.Length > 0) {
+
+				var item = _barItems.Children [_currentChild];
+				if (item == null) {
+					return true;
+				}
+				bool disabled = item == null || !item.IsEnabled ();
+				if (!disabled && (_host.UseSubMenusSingleFrame || !CheckSubMenu ())) {
+					SetNeedsDisplay ();
+					SetParentSetNeedsDisplay ();
+					return true;
+				}
+				if (!disabled) {
+					_host.OnMenuOpened ();
+				}
+
+			} else {
+				_host.SelectItem (_menuItemToSelect);
+			}
+		} else if (_host.IsMenuOpen) {
+			_host.CloseAllMenus ();
+		} else {
+			_host.OpenMenu ();
+		}
+		//_openedByHotKey = true;
+		return true;
+	}
+
+	/// <inheritdoc/>
+	public override bool? OnInvokingKeyBindings (Key keyEvent)
+	{
+		// This is a bit of a hack. We want to handle the key bindings for menu bar but
+		// InvokeKeyBindings doesn't pass any context so we can't tell which item it is for.
+		// So before we call the base class we set SelectedItem appropriately.
+
+		var key = keyEvent.KeyCode;
+
+		if (KeyBindings.TryGet(key, out _)) {
+			_menuBarItemToActivate = -1;
+			_menuItemToSelect = null;
+
+			var children = _barItems.Children;
+			if (children == null) {
+				return base.OnInvokingKeyBindings (keyEvent);
+			}
+
+			// Search for shortcuts first. If there's a shortcut, we don't want to activate the menu item.
+			foreach (var c in children) {
+				if (key == c?.Shortcut) {
+					_menuBarItemToActivate = -1;
+					_menuItemToSelect = c;
+					keyEvent.Scope = KeyBindingScope.HotKey;
+					return base.OnInvokingKeyBindings (keyEvent);
+				}
+				var subMenu = _barItems.SubMenu (c);
+				if (FindShortcutInChildMenu (key, subMenu)) {
+					keyEvent.Scope = KeyBindingScope.HotKey;
+					return base.OnInvokingKeyBindings (keyEvent);
+				}
+			}
+
+			// Search for hot keys next.
+			for (int c = 0; c < children.Length; c++) {
+				int hotKeyValue = children [c]?.HotKey.Value ?? default;
+				var hotKey = (KeyCode)hotKeyValue;
+				if (hotKey == KeyCode.Null) {
+					continue;
+				}
+				bool matches = key == hotKey || key == (hotKey | KeyCode.AltMask);
+				if (!_host.IsMenuOpen) {
+					// If the menu is open, only match if Alt is not pressed.
+					matches = key == hotKey;
+				}
+
+				if (matches) {
+					_menuItemToSelect = children [c];
+					_currentChild = c;
+					return base.OnInvokingKeyBindings (keyEvent);
+				}
+			}
+		}
+
+		var handled = base.OnInvokingKeyBindings (keyEvent);
+		if (handled != null && (bool)handled) {
+			return true;
+		}
+
+		// This supports the case where the menu bar is a context menu
+		return _host.OnInvokingKeyBindings (keyEvent);
+	}
+
+	bool FindShortcutInChildMenu (KeyCode key, MenuBarItem menuBarItem)
+	{
+		if (menuBarItem == null || menuBarItem.Children == null) {
+			return false;
+		}
+		foreach (var menuItem in menuBarItem.Children) {
+			if (key == menuItem?.Shortcut) {
+				_menuBarItemToActivate = -1;
+				_menuItemToSelect = menuItem;
+				return true;
+			}
+			var subMenu = menuBarItem.SubMenu (menuItem);
+			FindShortcutInChildMenu (key, subMenu);
+		}
+		return false;
+	}
+
+	void Current_TerminalResized (object sender, SizeChangedEventArgs e)
+	{
+		if (_host.IsMenuOpen) {
+			_host.CloseAllMenus ();
+		}
+	}
+
+	/// <inheritdoc/>
+	public override void OnVisibleChanged ()
+	{
+		base.OnVisibleChanged ();
+		if (Visible) {
+			Application.MouseEvent += Application_RootMouseEvent;
+		} else {
+			Application.MouseEvent -= Application_RootMouseEvent;
+		}
+	}
+
+	void Application_RootMouseEvent (object sender, MouseEventEventArgs a)
+	{
+		if (a.MouseEvent.View is MenuBar) {
+			return;
+		}
+		var locationOffset = _host.GetScreenOffsetFromCurrent ();
+		if (SuperView != null && SuperView != Application.Current) {
+			locationOffset.X += SuperView.Border.Thickness.Left;
+			locationOffset.Y += SuperView.Border.Thickness.Top;
+		}
+		var view = FindDeepestView (this, a.MouseEvent.X + locationOffset.X, a.MouseEvent.Y + locationOffset.Y, out int rx, out int ry);
+		if (view == this) {
+			if (!Visible) {
+				throw new InvalidOperationException ("This shouldn't running on a invisible menu!");
+			}
+
+			var nme = new MouseEvent () {
+				X = rx,
+				Y = ry,
+				Flags = a.MouseEvent.Flags,
+				View = view
+			};
+			if (MouseEvent (nme) || a.MouseEvent.Flags == MouseFlags.Button1Pressed || a.MouseEvent.Flags == MouseFlags.Button1Released) {
+				a.MouseEvent.Handled = true;
+			}
+		}
+	}
+
+	internal Attribute DetermineColorSchemeFor (MenuItem item, int index)
+	{
+		if (item != null) {
+			if (index == _currentChild) {
+				return ColorScheme.Focus;
+			}
+			if (!item.IsEnabled ()) {
+				return ColorScheme.Disabled;
+			}
+		}
+		return GetNormalColor ();
+	}
+
+	public override void OnDrawContent (Rect contentArea)
+	{
+		if (_barItems.Children == null) {
+			return;
+		}
+		var savedClip = Driver.Clip;
+		Driver.Clip = new Rect (0, 0, Driver.Cols, Driver.Rows);
+		Driver.SetAttribute (GetNormalColor ());
+
+		OnDrawFrames ();
+		OnRenderLineCanvas ();
+
+		for (int i = Bounds.Y; i < _barItems.Children.Length; i++) {
+			if (i < 0) {
+				continue;
+			}
+			if (BoundsToScreen (Bounds).Y + i >= Driver.Rows) {
+				break;
+			}
+			var item = _barItems.Children [i];
+			Driver.SetAttribute (item == null ? GetNormalColor ()
+				: i == _currentChild ? ColorScheme.Focus : GetNormalColor ());
+			if (item == null && BorderStyle != LineStyle.None) {
+				Move (-1, i);
+				Driver.AddRune (Glyphs.LeftTee);
+			} else if (Frame.X < Driver.Cols) {
+				Move (0, i);
+			}
+
+			Driver.SetAttribute (DetermineColorSchemeFor (item, i));
+			for (int p = Bounds.X; p < Frame.Width - 2; p++) {
+				// This - 2 is for the border
+				if (p < 0) {
+					continue;
+				}
+				if (BoundsToScreen (Bounds).X + p >= Driver.Cols) {
+					break;
+				}
+				if (item == null) {
+					Driver.AddRune (Glyphs.HLine);
+				} else if (i == 0 && p == 0 && _host.UseSubMenusSingleFrame && item.Parent.Parent != null) {
+					Driver.AddRune (Glyphs.LeftArrow);
+				}
+				// This `- 3` is left border + right border + one row in from right
+				else if (p == Frame.Width - 3 && _barItems.SubMenu (_barItems.Children [i]) != null) {
+					Driver.AddRune (Glyphs.RightArrow);
+				} else {
+					Driver.AddRune ((Rune)' ');
+				}
+			}
+
+			if (item == null) {
+				if (BorderStyle != LineStyle.None && SuperView?.Frame.Right - Frame.X > Frame.Width) {
+					Move (Frame.Width - 2, i);
+					Driver.AddRune (Glyphs.RightTee);
+				}
+				continue;
+			}
+
+			string textToDraw = null;
+			var nullCheckedChar = Glyphs.NullChecked;
+			var checkChar = Glyphs.Selected;
+			var uncheckedChar = Glyphs.UnSelected;
+
+			if (item.CheckType.HasFlag (MenuItemCheckStyle.Checked)) {
+				checkChar = Glyphs.Checked;
+				uncheckedChar = Glyphs.UnChecked;
+			}
+
+			// Support Checked even though CheckType wasn't set
+			if (item.CheckType == MenuItemCheckStyle.Checked && item.Checked == null) {
+				textToDraw = $"{nullCheckedChar} {item.Title}";
+			} else if (item.Checked == true) {
+				textToDraw = $"{checkChar} {item.Title}";
+			} else if (item.CheckType.HasFlag (MenuItemCheckStyle.Checked) || item.CheckType.HasFlag (MenuItemCheckStyle.Radio)) {
+				textToDraw = $"{uncheckedChar} {item.Title}";
+			} else {
+				textToDraw = item.Title;
+			}
+
+			BoundsToScreen (0, i, out int vtsCol, out int vtsRow, false);
+			if (vtsCol < Driver.Cols) {
+				Driver.Move (vtsCol + 1, vtsRow);
+				if (!item.IsEnabled ()) {
+					DrawHotString (textToDraw, ColorScheme.Disabled, ColorScheme.Disabled);
+				} else if (i == 0 && _host.UseSubMenusSingleFrame && item.Parent.Parent != null) {
+					var tf = new TextFormatter () {
+						Alignment = TextAlignment.Centered,
+						HotKeySpecifier = MenuBar.HotKeySpecifier,
+						Text = textToDraw
+					};
+					// The -3 is left/right border + one space (not sure what for)
+					tf.Draw (BoundsToScreen (new Rect (1, i, Frame.Width - 3, 1)),
+						i == _currentChild ? ColorScheme.Focus : GetNormalColor (),
+						i == _currentChild ? ColorScheme.HotFocus : ColorScheme.HotNormal,
+						SuperView == null ? default : SuperView.BoundsToScreen (SuperView.Bounds));
+				} else {
+					DrawHotString (textToDraw,
+						i == _currentChild ? ColorScheme.HotFocus : ColorScheme.HotNormal,
+						i == _currentChild ? ColorScheme.Focus : GetNormalColor ());
+				}
+
+				// The help string
+				int l = item.ShortcutTag.GetColumns () == 0 ? item.Help.GetColumns () : item.Help.GetColumns () + item.ShortcutTag.GetColumns () + 2;
+				int col = Frame.Width - l - 3;
+				BoundsToScreen (col, i, out vtsCol, out vtsRow, false);
+				if (vtsCol < Driver.Cols) {
+					Driver.Move (vtsCol, vtsRow);
+					Driver.AddStr (item.Help);
+
+					// The shortcut tag string
+					if (!string.IsNullOrEmpty (item.ShortcutTag)) {
+						Driver.Move (vtsCol + l - item.ShortcutTag.GetColumns (), vtsRow);
+						Driver.AddStr (item.ShortcutTag);
+					}
+				}
+			}
+		}
+		Driver.Clip = savedClip;
+
+		PositionCursor ();
+	}
+
+	void Current_DrawContentComplete (object sender, DrawEventArgs e)
+	{
+		if (Visible) {
+			OnDrawContent (Bounds);
+		}
+	}
+
+	public override void PositionCursor ()
+	{
+		if (_host == null || _host.IsMenuOpen) {
+			if (_barItems.IsTopLevel) {
+				_host.PositionCursor ();
+			} else {
+				Move (2, 1 + _currentChild);
+			}
+		} else {
+			_host.PositionCursor ();
+		}
+	}
+
+	public void Run (Action action)
+	{
+		if (action == null || _host == null) {
+			return;
+		}
+
+		Application.UngrabMouse ();
+		_host.CloseAllMenus ();
+		Application.Refresh ();
+
+		_host.Run (action);
+	}
+
+	public override bool OnLeave (View view)
+	{
+		return _host.OnLeave (view);
+	}
+
+	void RunSelected ()
+	{
+		if (_barItems.IsTopLevel) {
+			Run (_barItems.Action);
+		} else if (_currentChild > -1 && _barItems.Children [_currentChild].Action != null) {
+			Run (_barItems.Children [_currentChild].Action);
+		} else if (_currentChild == 0 && _host.UseSubMenusSingleFrame && _barItems.Children [_currentChild].Parent.Parent != null) {
+			_host.PreviousMenu (_barItems.Children [_currentChild].Parent.IsFromSubMenu, true);
+		} else if (_currentChild > -1 && _barItems.SubMenu (_barItems.Children [_currentChild]) != null) {
+			CheckSubMenu ();
+		}
+	}
+
+	void CloseAllMenus ()
+	{
+		Application.UngrabMouse ();
+		_host.CloseAllMenus ();
+	}
+
+	bool MoveDown ()
+	{
+		if (_barItems.IsTopLevel) {
+			return true;
+		}
+		bool disabled;
+		do {
+			_currentChild++;
+			if (_currentChild >= _barItems.Children.Length) {
+				_currentChild = 0;
+			}
+			if (this != _host.openCurrentMenu && _barItems.Children [_currentChild]?.IsFromSubMenu == true && _host._selectedSub > -1) {
+				_host.PreviousMenu (true);
+				_host.SelectEnabledItem (_barItems.Children, _currentChild, out _currentChild);
+				_host.openCurrentMenu = this;
+			}
+			var item = _barItems.Children [_currentChild];
+			if (item?.IsEnabled () != true) {
+				disabled = true;
+			} else {
+				disabled = false;
+			}
+			if (!_host.UseSubMenusSingleFrame && _host.UseKeysUpDownAsKeysLeftRight && _barItems.SubMenu (_barItems.Children [_currentChild]) != null &&
+			!disabled && _host.IsMenuOpen) {
+				if (!CheckSubMenu ()) {
+					return false;
+				}
+				break;
+			}
+			if (!_host.IsMenuOpen) {
+				_host.OpenMenu (_host._selected);
+			}
+		} while (_barItems.Children [_currentChild] == null || disabled);
+		SetNeedsDisplay ();
+		SetParentSetNeedsDisplay ();
+		if (!_host.UseSubMenusSingleFrame) {
+			_host.OnMenuOpened ();
+		}
+		return true;
+	}
+
+	bool MoveUp ()
+	{
+		if (_barItems.IsTopLevel || _currentChild == -1) {
+			return true;
+		}
+		bool disabled;
+		do {
+			_currentChild--;
+			if (_host.UseKeysUpDownAsKeysLeftRight && !_host.UseSubMenusSingleFrame) {
+				if ((_currentChild == -1 || this != _host.openCurrentMenu) && _barItems.Children [_currentChild + 1].IsFromSubMenu && _host._selectedSub > -1) {
+					_currentChild++;
+					_host.PreviousMenu (true);
+					if (_currentChild > 0) {
+						_currentChild--;
+						_host.openCurrentMenu = this;
+					}
+					break;
+				}
+			}
+			if (_currentChild < 0) {
+				_currentChild = _barItems.Children.Length - 1;
+			}
+			if (!_host.SelectEnabledItem (_barItems.Children, _currentChild, out _currentChild, false)) {
+				_currentChild = 0;
+				if (!_host.SelectEnabledItem (_barItems.Children, _currentChild, out _currentChild) && !_host.CloseMenu (false)) {
+					return false;
+				}
+				break;
+			}
+			var item = _barItems.Children [_currentChild];
+			if (item?.IsEnabled () != true) {
+				disabled = true;
+			} else {
+				disabled = false;
+			}
+			if (!_host.UseSubMenusSingleFrame && _host.UseKeysUpDownAsKeysLeftRight &&
+			_barItems.SubMenu (_barItems.Children [_currentChild]) != null &&
+			!disabled && _host.IsMenuOpen) {
+				if (!CheckSubMenu ()) {
+					return false;
+				}
+				break;
+			}
+		} while (_barItems.Children [_currentChild] == null || disabled);
+		SetNeedsDisplay ();
+		SetParentSetNeedsDisplay ();
+		if (!_host.UseSubMenusSingleFrame) {
+			_host.OnMenuOpened ();
+		}
+		return true;
+	}
+
+	void SetParentSetNeedsDisplay ()
+	{
+		if (_host._openSubMenu != null) {
+			foreach (var menu in _host._openSubMenu) {
+				menu.SetNeedsDisplay ();
+			}
+		}
+
+		_host?._openMenu?.SetNeedsDisplay ();
+		_host.SetNeedsDisplay ();
+	}
+
+	public override bool MouseEvent (MouseEvent me)
+	{
+		if (!_host._handled && !_host.HandleGrabView (me, this)) {
+			return false;
+		}
+		_host._handled = false;
+		bool disabled;
+		int meY = me.Y - (Border == null ? 0 : Border.Thickness.Top);
+		if (me.Flags == MouseFlags.Button1Clicked) {
+			disabled = false;
+			if (meY < 0) {
+				return true;
+			}
+			if (meY >= _barItems.Children.Length) {
+				return true;
+			}
+			var item = _barItems.Children [meY];
+			if (item == null || !item.IsEnabled ()) {
+				disabled = true;
+			}
+			if (disabled) {
+				return true;
+			}
+			_currentChild = meY;
+			if (item != null && !disabled) {
+				RunSelected ();
+			}
+			return true;
+		} else if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked ||
+			me.Flags == MouseFlags.Button1TripleClicked || me.Flags == MouseFlags.ReportMousePosition ||
+			me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) {
+
+			disabled = false;
+			if (meY < 0 || meY >= _barItems.Children.Length) {
+				return true;
+			}
+			var item = _barItems.Children [meY];
+			if (item == null) {
+				return true;
+			}
+			if (item == null || !item.IsEnabled ()) {
+				disabled = true;
+			}
+			if (item != null && !disabled) {
+				_currentChild = meY;
+			}
+			if (_host.UseSubMenusSingleFrame || !CheckSubMenu ()) {
+				SetNeedsDisplay ();
+				SetParentSetNeedsDisplay ();
+				return true;
+			}
+			_host.OnMenuOpened ();
+			return true;
+		}
+		return false;
+	}
+
+	internal bool CheckSubMenu ()
+	{
+		if (_currentChild == -1 || _barItems.Children [_currentChild] == null) {
+			return true;
+		}
+		var subMenu = _barItems.SubMenu (_barItems.Children [_currentChild]);
+		if (subMenu != null) {
+			int pos = -1;
+			if (_host._openSubMenu != null) {
+				pos = _host._openSubMenu.FindIndex (o => o?._barItems == subMenu);
+			}
+			if (pos == -1 && this != _host.openCurrentMenu && subMenu.Children != _host.openCurrentMenu._barItems.Children
+			&& !_host.CloseMenu (false, true)) {
+				return false;
+			}
+			_host.Activate (_host._selected, pos, subMenu);
+		} else if (_host._openSubMenu?.Count == 0 || _host._openSubMenu?.Last ()._barItems.IsSubMenuOf (_barItems.Children [_currentChild]) == false) {
+			return _host.CloseMenu (false, true);
+		} else {
+			SetNeedsDisplay ();
+			SetParentSetNeedsDisplay ();
+		}
+		return true;
+	}
+
+	int GetSubMenuIndex (MenuBarItem subMenu)
+	{
+		int pos = -1;
+		if (this != null && Subviews.Count > 0) {
+			Menu v = null;
+			foreach (var menu in Subviews) {
+				if (((Menu)menu)._barItems == subMenu) {
+					v = (Menu)menu;
+				}
+			}
+			if (v != null) {
+				pos = Subviews.IndexOf (v);
+			}
+		}
+
+		return pos;
+	}
+
+	///<inheritdoc/>
+	public override bool OnEnter (View view)
+	{
+		Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
+
+		return base.OnEnter (view);
+	}
+
+	protected override void Dispose (bool disposing)
+	{
+		if (Application.Current != null) {
+			Application.Current.DrawContentComplete -= Current_DrawContentComplete;
+			Application.Current.SizeChanging -= Current_TerminalResized;
+		}
+		Application.MouseEvent -= Application_RootMouseEvent;
+		base.Dispose (disposing);
+	}
+}

+ 1467 - 0
Terminal.Gui/Views/Menu/MenuBar.cs

@@ -0,0 +1,1467 @@
+using System;
+using System.Text;
+using System.Linq;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace Terminal.Gui;
+
+/// <summary>
+/// <see cref="MenuBarItem"/> is a menu item on  <see cref="MenuBar"/>. 
+/// MenuBarItems do not support <see cref="MenuItem.Shortcut"/>.
+/// </summary>
+public class MenuBarItem : MenuItem {
+	/// <summary>
+	/// Initializes a new <see cref="MenuBarItem"/> as a <see cref="MenuItem"/>.
+	/// </summary>
+	/// <param name="title">Title for the menu item.</param>
+	/// <param name="help">Help text to display. Will be displayed next to the Title surrounded by parentheses.</param>
+	/// <param name="action">Action to invoke when the menu item is activated.</param>
+	/// <param name="canExecute">Function to determine if the action can currently be executed.</param>
+	/// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
+	public MenuBarItem (string title, string help, Action action, Func<bool> canExecute = null, MenuItem parent = null) : base (title, help, action, canExecute, parent)
+	{
+		Initialize (title, null, null, true);
+	}
+
+	/// <summary>
+	/// Initializes a new <see cref="MenuBarItem"/>.
+	/// </summary>
+	/// <param name="title">Title for the menu item.</param>
+	/// <param name="children">The items in the current menu.</param>
+	/// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
+	public MenuBarItem (string title, MenuItem [] children, MenuItem parent = null)
+	{
+		Initialize (title, children, parent);
+	}
+
+	/// <summary>
+	/// Initializes a new <see cref="MenuBarItem"/> with separate list of items.
+	/// </summary>
+	/// <param name="title">Title for the menu item.</param>
+	/// <param name="children">The list of items in the current menu.</param>
+	/// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
+	public MenuBarItem (string title, List<MenuItem []> children, MenuItem parent = null)
+	{
+		Initialize (title, children, parent);
+	}
+
+	/// <summary>
+	/// Initializes a new <see cref="MenuBarItem"/>.
+	/// </summary>
+	/// <param name="children">The items in the current menu.</param>
+	public MenuBarItem (MenuItem [] children) : this ("", children) { }
+
+	/// <summary>
+	/// Initializes a new <see cref="MenuBarItem"/>.
+	/// </summary>
+	public MenuBarItem () : this (children: new MenuItem [] { }) { }
+
+	void Initialize (string title, object children, MenuItem parent = null, bool isTopLevel = false)
+	{
+		if (!isTopLevel && children == null) {
+			throw new ArgumentNullException (nameof (children), "The parameter cannot be null. Use an empty array instead.");
+		}
+		SetTitle (title ?? "");
+		if (parent != null) {
+			Parent = parent;
+		}
+		if (children is List<MenuItem []> childrenList) {
+			var newChildren = new MenuItem [] { };
+			foreach (var grandChild in childrenList) {
+				foreach (var child in grandChild) {
+					SetParent (grandChild);
+					Array.Resize (ref newChildren, newChildren.Length + 1);
+					newChildren [newChildren.Length - 1] = child;
+				}
+
+			}
+			Children = newChildren;
+		} else if (children is MenuItem [] items) {
+			SetParent (items);
+			Children = items;
+		} else {
+			Children = null;
+		}
+	}
+
+	void SetParent (MenuItem [] children)
+	{
+		foreach (var child in children) {
+			if (child is { Parent: null }) {
+				child.Parent = this;
+			}
+		}
+	}
+
+	/// <summary>
+	/// Check if a <see cref="MenuItem"/> is a <see cref="MenuBarItem"/>.
+	/// </summary>
+	/// <param name="menuItem"></param>
+	/// <returns>Returns a <see cref="MenuBarItem"/> or null otherwise.</returns>
+	public MenuBarItem SubMenu (MenuItem menuItem)
+	{
+		return menuItem as MenuBarItem;
+	}
+
+	/// <summary>
+	/// Check if a <see cref="MenuItem"/> is a submenu of this MenuBar.
+	/// </summary>
+	/// <param name="menuItem"></param>
+	/// <returns>Returns <c>true</c> if it is a submenu. <c>false</c> otherwise.</returns>
+	public bool IsSubMenuOf (MenuItem menuItem)
+	{
+		foreach (var child in Children) {
+			if (child == menuItem && child.Parent == menuItem.Parent) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/// <summary>
+	/// Get the index of a child <see cref="MenuItem"/>.
+	/// </summary>
+	/// <param name="children"></param>
+	/// <returns>Returns a greater than -1 if the <see cref="MenuItem"/> is a child.</returns>
+	public int GetChildrenIndex (MenuItem children)
+	{
+		int i = 0;
+		if (Children != null) {
+			foreach (var child in Children) {
+				if (child == children) {
+					return i;
+				}
+				i++;
+			}
+		}
+		return -1;
+	}
+
+	void SetTitle (string title)
+	{
+		title ??= string.Empty;
+		Title = title;
+	}
+
+	/// <summary>
+	/// Gets or sets an array of <see cref="MenuItem"/> objects that are the children of this <see cref="MenuBarItem"/>
+	/// </summary>
+	/// <value>The children.</value>
+	public MenuItem [] Children { get; set; }
+
+	internal bool IsTopLevel => Parent == null && (Children == null || Children.Length == 0) && Action != null;
+
+	internal void AddKeyBindings (MenuBar menuBar)
+	{
+		if (Children == null) {
+			return;
+		}
+		foreach (var menuItem in Children.Where (m => m != null)) {
+			if (menuItem.HotKey != default) {
+				menuBar.KeyBindings.Add ((KeyCode)menuItem.HotKey.Value, Command.ToggleExpandCollapse);
+				menuBar.KeyBindings.Add ((KeyCode)menuItem.HotKey.Value | KeyCode.AltMask, KeyBindingScope.HotKey, Command.ToggleExpandCollapse);
+			}
+			if (menuItem.Shortcut != KeyCode.Unknown && menuItem.Shortcut != KeyCode.Null) {
+				menuBar.KeyBindings.Add (menuItem.Shortcut, KeyBindingScope.HotKey, Command.Select);
+			}
+			SubMenu (menuItem)?.AddKeyBindings (menuBar);
+		}
+	}
+}
+
+/// <summary>
+/// <para>
+/// Provides a menu bar that spans the top of a <see cref="Toplevel"/> View with drop-down and cascading menus.
+/// </para>
+/// <para>
+/// By default, any sub-sub-menus (sub-menus of the <see cref="MenuItem"/>s added to <see cref="MenuBarItem"/>s) 
+/// are displayed in a cascading manner, where each sub-sub-menu pops out of the sub-menu frame
+/// (either to the right or left, depending on where the sub-menu is relative to the edge of the screen). By setting
+/// <see cref="UseSubMenusSingleFrame"/> to <see langword="true"/>, this behavior can be changed such that all sub-sub-menus are
+/// drawn within a single frame below the MenuBar.
+/// </para>
+/// </summary>
+/// <remarks>
+/// <para>
+/// The <see cref="MenuBar"/> appears on the first row of the <see cref="Toplevel"/> SuperView and uses the full width.
+/// </para>
+/// <para>
+/// See also: <see cref="ContextMenu"/>
+/// </para>
+/// <para>
+/// The <see cref="MenuBar"/> provides global hot keys for the application. See <see cref="MenuItem.HotKey"/>.
+/// </para>
+/// <para>
+/// When the menu is created key bindings for each menu item and its sub-menu items are added for each menu item's
+/// hot key (both alone AND with AltMask) and shortcut, if defined.
+/// </para>
+/// <para>
+/// If a key press matches any of the menu item's hot keys or shortcuts, the menu item's action is invoked or
+/// sub-menu opened.
+/// </para>
+/// <para>
+/// * If the menu bar is not open
+///   * Any shortcut defined within the menu will be invoked
+///   * Only hot keys defined for the menu bar items will be invoked, and only if Alt is pressed too.
+/// * If the menu bar is open
+///   * Un-shifted hot keys defined for the menu bar items will be invoked, only if the menu they belong to is open (the menu bar item's text is visible).
+///   * Alt-shifted hot keys defined for the menu bar items will be invoked, only if the menu they belong to is open (the menu bar item's text is visible).
+///   * If there is a visible hot key that duplicates a shortcut (e.g. _File and Alt-F), the hot key wins.
+/// </para>
+/// </remarks>
+public class MenuBar : View {
+	internal int _selected;
+	internal int _selectedSub;
+
+	/// <summary>
+	/// Gets or sets the array of <see cref="MenuBarItem"/>s for the menu. Only set this after the <see cref="MenuBar"/> is visible.
+	/// </summary>
+	/// <value>The menu array.</value>
+	public MenuBarItem [] Menus { get; set; }
+
+	/// <summary>
+	/// The default <see cref="LineStyle"/> for <see cref="Menus"/>'s border. The default is <see cref="LineStyle.Single"/>.
+	/// </summary>
+	public LineStyle MenusBorderStyle { get; set; } = LineStyle.Single;
+
+	bool _useSubMenusSingleFrame;
+
+	/// <summary>
+	/// Gets or sets if the sub-menus must be displayed in a single or multiple frames.
+	/// <para>
+	/// By default any sub-sub-menus (sub-menus of the main <see cref="MenuItem"/>s) are displayed in a cascading manner, 
+	/// where each sub-sub-menu pops out of the sub-menu frame
+	/// (either to the right or left, depending on where the sub-menu is relative to the edge of the screen). By setting
+	/// <see cref="UseSubMenusSingleFrame"/> to <see langword="true"/>, this behavior can be changed such that all sub-sub-menus are
+	/// drawn within a single frame below the MenuBar.
+	/// </para>		
+	/// </summary>
+	public bool UseSubMenusSingleFrame {
+		get => _useSubMenusSingleFrame;
+		set {
+			_useSubMenusSingleFrame = value;
+			if (value && UseKeysUpDownAsKeysLeftRight) {
+				_useKeysUpDownAsKeysLeftRight = false;
+				SetNeedsDisplay ();
+			}
+		}
+	}
+
+	///<inheritdoc/>
+	public override bool Visible {
+		get => base.Visible;
+		set {
+			base.Visible = value;
+			if (!value) {
+				CloseAllMenus ();
+			}
+		}
+	}
+
+	/// <summary>
+	/// Initializes a new instance of the <see cref="MenuBar"/>.
+	/// </summary>
+	public MenuBar () : this (new MenuBarItem [] { }) { }
+
+	/// <summary>
+	/// Initializes a new instance of the <see cref="MenuBar"/> class with the specified set of Toplevel menu items.
+	/// </summary>
+	/// <param name="menus">Individual menu items; a null item will result in a separator being drawn.</param>
+	public MenuBar (MenuBarItem [] menus) : base ()
+	{
+		X = 0;
+		Y = 0;
+		Width = Dim.Fill ();
+		Height = 1;
+		Menus = menus;
+		//CanFocus = true;
+		_selected = -1;
+		_selectedSub = -1;
+		ColorScheme = Colors.Menu;
+		WantMousePositionReports = true;
+		IsMenuOpen = false;
+
+		Added += MenuBar_Added;
+
+		// Things this view knows how to do
+		AddCommand (Command.Left, () => {
+			MoveLeft ();
+			return true;
+		});
+		AddCommand (Command.Right, () => {
+			MoveRight ();
+			return true;
+		});
+		AddCommand (Command.Cancel, () => {
+			CloseMenuBar ();
+			return true;
+		});
+		AddCommand (Command.Accept, () => {
+			ProcessMenu (_selected, Menus [_selected]);
+			return true;
+		});
+
+		AddCommand (Command.ToggleExpandCollapse, () => SelectOrRun ());
+		AddCommand (Command.Select, () => Run (_menuItemToSelect?.Action));
+
+		// Default key bindings for this view
+		KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
+		KeyBindings.Add (KeyCode.CursorRight, Command.Right);
+		KeyBindings.Add (KeyCode.Esc, Command.Cancel);
+		KeyBindings.Add (KeyCode.CursorDown, Command.Accept);
+		KeyBindings.Add (KeyCode.Enter, Command.Accept);
+		KeyBindings.Add ((KeyCode)Key, KeyBindingScope.HotKey, Command.ToggleExpandCollapse);
+		KeyBindings.Add (KeyCode.CtrlMask | KeyCode.Space, KeyBindingScope.HotKey, Command.ToggleExpandCollapse);
+
+		// TODO: Bindings (esp for hotkey) should be added across and then down. This currently does down then across. 
+		// TODO: As a result, _File._Save will have precedence over in "_File _Edit _ScrollbarView"
+		// TODO: Also: Hotkeys should not work for sub-menus if they are not visible!
+		if (Menus != null) {
+			foreach (var menuBarItem in Menus?.Where (m => m != null)) {
+				if (menuBarItem.HotKey != default) {
+					KeyBindings.Add ((KeyCode)menuBarItem.HotKey.Value, Command.ToggleExpandCollapse);
+					KeyBindings.Add ((KeyCode)menuBarItem.HotKey.Value | KeyCode.AltMask, KeyBindingScope.HotKey, Command.ToggleExpandCollapse);
+				}
+				if (menuBarItem.Shortcut != KeyCode.Unknown && menuBarItem.Shortcut != KeyCode.Null) {
+					// Technically this will will never run because MenuBarItems don't have shortcuts
+					KeyBindings.Add (menuBarItem.Shortcut, KeyBindingScope.HotKey, Command.Select);
+				}
+				menuBarItem.AddKeyBindings (this);
+			}
+		}
+
+#if SUPPORT_ALT_TO_ACTIVATE_MENU
+		// Enable the Alt key as a menu activator
+		Initialized += (s, e) => {
+			if (SuperView != null) {
+				SuperView.KeyUp += SuperView_KeyUp;
+			}
+		};
+#endif       
+	}
+
+#if SUPPORT_ALT_TO_ACTIVATE_MENU
+	void SuperView_KeyUp (object sender, KeyEventArgs e)
+	{
+		if (SuperView == null || SuperView.CanFocus == false || SuperView.Visible == false) {
+			return;
+		}
+		AltKeyUpHandler(e);
+	}
+#endif
+	
+	internal void AltKeyUpHandler (Key e)
+	{
+		if (e.KeyCode == KeyCode.AltMask) {
+			e.Handled = true;
+			// User pressed Alt 
+			if (!IsMenuOpen && _openMenu == null && !_openedByAltKey) {
+				// There's no open menu, the first menu item should be highlighted.
+				// The right way to do this is to SetFocus(MenuBar), but for some reason
+				// that faults.
+
+				GetMouseGrabViewInstance (this)?.CleanUp ();
+
+				IsMenuOpen = true;
+				_openedByAltKey = true;
+				_selected = 0;
+				CanFocus = true;
+				_lastFocused = SuperView == null ? Application.Current.MostFocused : SuperView.MostFocused;
+				SetFocus ();
+				SetNeedsDisplay ();
+				Application.GrabMouse (this);
+			} else if (!_openedByHotKey) {
+				// There's an open menu. Close it.
+				CleanUp ();
+			} else {
+				_openedByAltKey = false;
+				_openedByHotKey = false;
+			}
+		}
+	}
+
+	#region Keyboard handling
+	Key _key = Key.F9;
+
+	/// <summary>
+	/// The <see cref="Key"/> used to activate or close the menu bar by keyboard. The default is <see cref="Key.F9"/>.
+	/// </summary>
+	/// <remarks>
+	/// <para>
+	/// If the user presses any <see cref="MenuItem.HotKey"/>s defined in the <see cref="MenuBarItem"/>s, the menu bar will be activated and the sub-menu will be opened.
+	/// </para>
+	/// <para>
+	/// <see cref="Key.Esc"/> will close the menu bar and any open sub-menus.
+	/// </para>
+	/// </remarks>
+	public Key Key {
+		get => _key;
+		set {
+			if (_key == value) {
+				return;
+			}
+			KeyBindings.Remove (_key);
+			KeyBindings.Add (value, KeyBindingScope.HotKey, Command.ToggleExpandCollapse);
+			_key = value;
+		}
+	}
+
+
+	bool _useKeysUpDownAsKeysLeftRight = false;
+
+	/// <summary>
+	/// Used for change the navigation key style.
+	/// </summary>
+	public bool UseKeysUpDownAsKeysLeftRight {
+		get => _useKeysUpDownAsKeysLeftRight;
+		set {
+			_useKeysUpDownAsKeysLeftRight = value;
+			if (value && UseSubMenusSingleFrame) {
+				UseSubMenusSingleFrame = false;
+				SetNeedsDisplay ();
+			}
+		}
+	}
+
+	static Rune _shortcutDelimiter = new Rune ('+');
+
+	/// <summary>
+	/// Sets or gets the shortcut delimiter separator. The default is "+".
+	/// </summary>
+	public static Rune ShortcutDelimiter {
+		get => _shortcutDelimiter;
+		set {
+			if (_shortcutDelimiter != value) {
+				_shortcutDelimiter = value == default ? new Rune ('+') : value;
+			}
+		}
+	}
+
+	/// <summary>
+	/// The specifier character for the hot keys.
+	/// </summary>
+	public new static Rune HotKeySpecifier => (Rune)'_';
+
+	// Set in OnInvokingKeyBindings. -1 means no menu item is selected for activation.
+	int _menuBarItemToActivate;
+
+	// Set in OnInvokingKeyBindings. null means no sub-menu is selected for activation.
+	MenuItem _menuItemToSelect;
+	bool _openedByAltKey;
+	bool _openedByHotKey;
+
+	/// <summary>
+	/// Called when a key bound to Command.Select is pressed. Either activates the menu item or runs it, depending on whether it has a sub-menu.
+	/// If the menu is open, it will close the menu bar.
+	/// </summary>
+	/// <returns></returns>
+	bool SelectOrRun ()
+	{
+		if (!IsInitialized || !Visible) {
+			return true;
+		}
+
+		_openedByHotKey = true;
+		if (_menuBarItemToActivate != -1) {
+			Activate (_menuBarItemToActivate);
+		} else if (_menuItemToSelect != null) {
+			Run (_menuItemToSelect.Action);
+		} else {
+			if (IsMenuOpen && _openMenu != null) {
+				CloseAllMenus ();
+			} else {
+				OpenMenu ();
+			}
+
+		}
+		return true;
+	}
+
+	/// <inheritdoc/>
+	public override bool? OnInvokingKeyBindings (Key keyEvent)
+	{
+		// This is a bit of a hack. We want to handle the key bindings for menu bar but
+		// InvokeKeyBindings doesn't pass any context so we can't tell which item it is for.
+		// So before we call the base class we set SelectedItem appropriately.
+		// TODO: Figure out if there's a way to have KeyBindings pass context instead. Maybe a KeyBindingContext property?
+
+		var key = keyEvent.KeyCode;
+
+		if (KeyBindings.TryGet (key, out _)) {
+			_menuBarItemToActivate = -1;
+			_menuItemToSelect = null;
+
+			// Search for shortcuts first. If there's a shortcut, we don't want to activate the menu item.
+			for (int i = 0; i < Menus.Length; i++) {
+				// Recurse through the menu to find one with the shortcut.
+				if (FindShortcutInChildMenu (key, Menus [i], out _menuItemToSelect)) {
+					_menuBarItemToActivate = i;
+					keyEvent.Scope = KeyBindingScope.HotKey;
+					return base.OnInvokingKeyBindings (keyEvent);
+				}
+
+				// Now see if any of the menu bar items have a hot key that matches
+				// Technically this is not possible because menu bar items don't have 
+				// shortcuts or Actions. But it's here for completeness. 
+				var shortcut = Menus [i]?.Shortcut;
+				if (key == shortcut) {
+					throw new InvalidOperationException ("Menu bar items cannot have shortcuts");
+				}
+
+			}
+
+			// Search for hot keys next.
+			for (int i = 0; i < Menus.Length; i++) {
+				if (IsMenuOpen) {
+					// We don't need to do anything because `Menu` will handle the key binding.
+					//break;
+				}
+
+				// No submenu item matched (or the menu is closed)
+
+				// Check if one of the menu bar item has a hot key that matches
+				int hotKeyValue = Menus [i]?.HotKey.Value ?? default;
+				var hotKey = (KeyCode)hotKeyValue;
+				if (hotKey != KeyCode.Null) {
+					bool matches = key == hotKey || key == (hotKey | KeyCode.AltMask);
+					if (IsMenuOpen) {
+						// If the menu is open, only match if Alt is not pressed.
+						matches = key == hotKey;
+					}
+
+					if (matches) {
+						_menuBarItemToActivate = i;
+						keyEvent.Scope = KeyBindingScope.HotKey;
+						break;
+					}
+				}
+
+			}
+		}
+		return base.OnInvokingKeyBindings (keyEvent);
+	}
+
+	// TODO: Update to use Key instead of KeyCode
+	// Recurse the child menus looking for a shortcut that matches the key
+	bool FindShortcutInChildMenu (KeyCode key, MenuBarItem menuBarItem, out MenuItem menuItemToSelect)
+	{
+		menuItemToSelect = null;
+
+		if (key == KeyCode.Null || menuBarItem?.Children == null) {
+			return false;
+		}
+
+		for (int c = 0; c < menuBarItem.Children.Length; c++) {
+			var menuItem = menuBarItem.Children [c];
+			if (key == menuItem?.Shortcut) {
+				menuItemToSelect = menuItem;
+				return true;
+			}
+			var subMenu = menuBarItem.SubMenu (menuItem);
+			if (subMenu != null) {
+				if (FindShortcutInChildMenu (key, subMenu, out menuItemToSelect)) {
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+#endregion Keyboard handling
+
+	bool _initialCanFocus;
+
+	void MenuBar_Added (object sender, SuperViewChangedEventArgs e)
+	{
+		_initialCanFocus = CanFocus;
+		Added -= MenuBar_Added;
+	}
+
+	bool _isCleaning;
+
+	internal void CleanUp ()
+	{
+		_isCleaning = true;
+		if (_openMenu != null) {
+			CloseAllMenus ();
+		}
+		_openedByAltKey = false;
+		_openedByHotKey = false;
+		IsMenuOpen = false;
+		_selected = -1;
+		CanFocus = _initialCanFocus;
+		if (_lastFocused != null) {
+			_lastFocused.SetFocus ();
+		}
+		SetNeedsDisplay ();
+		Application.UngrabMouse ();
+		_isCleaning = false;
+	}
+
+	// The column where the MenuBar starts
+	static int _xOrigin = 0;
+
+	// Spaces before the Title
+	static int _leftPadding = 1;
+
+	// Spaces after the Title
+	static int _rightPadding = 1;
+
+	// Spaces after the submenu Title, before Help
+	static int _parensAroundHelp = 3;
+
+	///<inheritdoc/>
+	public override void OnDrawContent (Rect contentArea)
+	{
+		Move (0, 0);
+		Driver.SetAttribute (GetNormalColor ());
+		for (int i = 0; i < Frame.Width; i++) {
+			Driver.AddRune ((Rune)' ');
+		}
+
+		Move (1, 0);
+		int pos = 0;
+
+		for (int i = 0; i < Menus.Length; i++) {
+			var menu = Menus [i];
+			Move (pos, 0);
+			Attribute hotColor, normalColor;
+			if (i == _selected && IsMenuOpen) {
+				hotColor = i == _selected ? ColorScheme.HotFocus : ColorScheme.HotNormal;
+				normalColor = i == _selected ? ColorScheme.Focus : GetNormalColor ();
+			} else {
+				hotColor = ColorScheme.HotNormal;
+				normalColor = GetNormalColor ();
+			}
+			// Note Help on MenuBar is drawn with parens around it
+			DrawHotString (string.IsNullOrEmpty (menu.Help) ? $" {menu.Title} " : $" {menu.Title} ({menu.Help}) ", hotColor, normalColor);
+			pos += _leftPadding + menu.TitleLength + (menu.Help.GetColumns () > 0 ? _leftPadding + menu.Help.GetColumns () + _parensAroundHelp : 0) + _rightPadding;
+		}
+		PositionCursor ();
+	}
+
+	///<inheritdoc/>
+	public override void PositionCursor ()
+	{
+		if (_selected == -1 && HasFocus && Menus.Length > 0) {
+			_selected = 0;
+		}
+		int pos = 0;
+		for (int i = 0; i < Menus.Length; i++) {
+			if (i == _selected) {
+				pos++;
+				Move (pos + 1, 0);
+				return;
+			} else {
+				pos += _leftPadding + Menus [i].TitleLength + (Menus [i].Help.GetColumns () > 0 ? Menus [i].Help.GetColumns () + _parensAroundHelp : 0) + _rightPadding;
+			}
+		}
+	}
+
+	/// <summary>
+	/// Called when an item is selected; Runs the action.
+	/// </summary>
+	/// <param name="item"></param>
+	internal bool SelectItem (MenuItem item)
+	{
+		if (item?.Action == null) {
+			return false;
+		}
+
+		Application.UngrabMouse ();
+		CloseAllMenus ();
+		Application.Refresh ();
+		_openedByAltKey = true;
+		return Run (item?.Action);
+	}
+
+	internal bool Run (Action action)
+	{
+		if (action == null) {
+			return false;
+		}
+		Application.MainLoop.AddIdle (() => {
+			action ();
+			return false;
+		});
+		return true;
+	}
+
+	/// <summary>
+	/// Raised as a menu is opening.
+	/// </summary>
+	public event EventHandler<MenuOpeningEventArgs> MenuOpening;
+
+	/// <summary>
+	/// Raised when a menu is opened.
+	/// </summary>
+	public event EventHandler<MenuOpenedEventArgs> MenuOpened;
+
+	/// <summary>
+	/// Raised when a menu is closing passing <see cref="MenuClosingEventArgs"/>.
+	/// </summary>
+	public event EventHandler<MenuClosingEventArgs> MenuClosing;
+
+	/// <summary>
+	/// Raised when all the menu is closed.
+	/// </summary>
+	public event EventHandler MenuAllClosed;
+
+	// BUGBUG: Hack
+	internal Menu _openMenu;
+	Menu _ocm;
+
+	internal Menu openCurrentMenu {
+		get => _ocm;
+		set {
+			if (_ocm != value) {
+				_ocm = value;
+				if (_ocm != null && _ocm._currentChild > -1) {
+					OnMenuOpened ();
+				}
+			}
+		}
+	}
+
+	internal List<Menu> _openSubMenu;
+	View _previousFocused;
+	internal bool _isMenuOpening;
+	internal bool _isMenuClosing;
+
+	/// <summary>
+	/// <see langword="true"/> if the menu is open; otherwise <see langword="true"/>.
+	/// </summary>
+	public bool IsMenuOpen { get; protected set; }
+
+	/// <summary>
+	/// Virtual method that will invoke the <see cref="MenuOpening"/> event if it's defined.
+	/// </summary>
+	/// <param name="currentMenu">The current menu to be replaced.</param>
+	/// <returns>Returns the <see cref="MenuOpeningEventArgs"/></returns>
+	public virtual MenuOpeningEventArgs OnMenuOpening (MenuBarItem currentMenu)
+	{
+		var ev = new MenuOpeningEventArgs (currentMenu);
+		MenuOpening?.Invoke (this, ev);
+		return ev;
+	}
+
+	/// <summary>
+	/// Virtual method that will invoke the <see cref="MenuOpened"/> event if it's defined.
+	/// </summary>
+	public virtual void OnMenuOpened ()
+	{
+		MenuItem mi = null;
+		MenuBarItem parent;
+
+		if (openCurrentMenu._barItems.Children != null && openCurrentMenu._barItems.Children.Length > 0
+								&& openCurrentMenu?._currentChild > -1) {
+			parent = openCurrentMenu._barItems;
+			mi = parent.Children [openCurrentMenu._currentChild];
+		} else if (openCurrentMenu._barItems.IsTopLevel) {
+			parent = null;
+			mi = openCurrentMenu._barItems;
+		} else {
+			parent = _openMenu._barItems;
+			mi = parent.Children [_openMenu._currentChild];
+		}
+		MenuOpened?.Invoke (this, new MenuOpenedEventArgs (parent, mi));
+	}
+
+	/// <summary>
+	/// Virtual method that will invoke the <see cref="MenuClosing"/>.
+	/// </summary>
+	/// <param name="currentMenu">The current menu to be closed.</param>
+	/// <param name="reopen">Whether the current menu will be reopen.</param>
+	/// <param name="isSubMenu">Whether is a sub-menu or not.</param>
+	public virtual MenuClosingEventArgs OnMenuClosing (MenuBarItem currentMenu, bool reopen, bool isSubMenu)
+	{
+		var ev = new MenuClosingEventArgs (currentMenu, reopen, isSubMenu);
+		MenuClosing?.Invoke (this, ev);
+		return ev;
+	}
+
+	/// <summary>
+	/// Virtual method that will invoke the <see cref="MenuAllClosed"/>.
+	/// </summary>
+	public virtual void OnMenuAllClosed ()
+	{
+		MenuAllClosed?.Invoke (this, EventArgs.Empty);
+	}
+
+	View _lastFocused;
+
+	/// <summary>
+	/// Gets the view that was last focused before opening the menu.
+	/// </summary>
+	public View LastFocused { get; private set; }
+
+	internal void OpenMenu (int index, int sIndex = -1, MenuBarItem subMenu = null)
+	{
+		_isMenuOpening = true;
+		var newMenu = OnMenuOpening (Menus [index]);
+		if (newMenu.Cancel) {
+			_isMenuOpening = false;
+			return;
+		}
+		if (newMenu.NewMenuBarItem != null) {
+			Menus [index] = newMenu.NewMenuBarItem;
+		}
+		int pos = 0;
+		switch (subMenu) {
+		case null:
+			// Open a submenu below a MenuBar
+			_lastFocused ??= SuperView == null ? Application.Current?.MostFocused : SuperView.MostFocused;
+			if (_openSubMenu != null && !CloseMenu (false, true)) {
+				return;
+			}
+			if (_openMenu != null) {
+				Application.Current.Remove (_openMenu);
+				_openMenu.Dispose ();
+				_openMenu = null;
+			}
+
+			// This positions the submenu horizontally aligned with the first character of the
+			// text belonging to the menu 
+			for (int i = 0; i < index; i++) {
+				pos += Menus [i].TitleLength + (Menus [i].Help.GetColumns () > 0 ? Menus [i].Help.GetColumns () + 2 : 0) + _leftPadding + _rightPadding;
+			}
+
+			var locationOffset = Point.Empty;
+			// if SuperView is null then it's from a ContextMenu
+			if (SuperView == null) {
+				locationOffset = GetScreenOffset ();
+			}
+			if (SuperView != null && SuperView != Application.Current) {
+				locationOffset.X += SuperView.Border.Thickness.Left;
+				locationOffset.Y += SuperView.Border.Thickness.Top;
+			}
+			_openMenu = new Menu (this, Frame.X + pos + locationOffset.X, Frame.Y + 1 + locationOffset.Y, Menus [index], null, MenusBorderStyle);
+			openCurrentMenu = _openMenu;
+			openCurrentMenu._previousSubFocused = _openMenu;
+
+			Application.Current.Add (_openMenu);
+			_openMenu.SetFocus ();
+			break;
+		default:
+			// Opens a submenu next to another submenu (openSubMenu)
+			if (_openSubMenu == null) {
+				_openSubMenu = new List<Menu> ();
+			}
+			if (sIndex > -1) {
+				RemoveSubMenu (sIndex);
+			} else {
+				var last = _openSubMenu.Count > 0 ? _openSubMenu.Last () : _openMenu;
+				if (!UseSubMenusSingleFrame) {
+					locationOffset = GetLocationOffset ();
+					openCurrentMenu = new Menu (this, last.Frame.Left + last.Frame.Width + locationOffset.X, last.Frame.Top + locationOffset.Y + last._currentChild, subMenu, last, MenusBorderStyle);
+				} else {
+					var first = _openSubMenu.Count > 0 ? _openSubMenu.First () : _openMenu;
+					// 2 is for the parent and the separator
+					var mbi = new MenuItem [2 + subMenu.Children.Length];
+					mbi [0] = new MenuItem () { Title = subMenu.Title, Parent = subMenu };
+					mbi [1] = null;
+					for (int j = 0; j < subMenu.Children.Length; j++) {
+						mbi [j + 2] = subMenu.Children [j];
+					}
+					var newSubMenu = new MenuBarItem (mbi) { Parent = subMenu };
+					openCurrentMenu = new Menu (this, first.Frame.Left, first.Frame.Top, newSubMenu, null, MenusBorderStyle);
+					last.Visible = false;
+					Application.GrabMouse (openCurrentMenu);
+				}
+				openCurrentMenu._previousSubFocused = last._previousSubFocused;
+				_openSubMenu.Add (openCurrentMenu);
+				Application.Current.Add (openCurrentMenu);
+			}
+			_selectedSub = _openSubMenu.Count - 1;
+			if (_selectedSub > -1 && SelectEnabledItem (openCurrentMenu._barItems.Children, openCurrentMenu._currentChild, out openCurrentMenu._currentChild)) {
+				openCurrentMenu.SetFocus ();
+			}
+			break;
+		}
+		_isMenuOpening = false;
+		IsMenuOpen = true;
+	}
+
+	Point GetLocationOffset ()
+	{
+		if (MenusBorderStyle != LineStyle.None) {
+			return new Point (0, 1);
+		}
+		return new Point (-2, 0);
+	}
+
+	/// <summary>
+	/// Opens the Menu programatically, as though the F9 key were pressed.
+	/// </summary>
+	public void OpenMenu ()
+	{
+		var mbar = GetMouseGrabViewInstance (this);
+		if (mbar != null) {
+			mbar.CleanUp ();
+		}
+
+		if (_openMenu != null) {
+			return;
+		}
+		_selected = 0;
+		SetNeedsDisplay ();
+
+		_previousFocused = SuperView == null ? Application.Current.Focused : SuperView.Focused;
+		OpenMenu (_selected);
+		if (!SelectEnabledItem (openCurrentMenu._barItems.Children, openCurrentMenu._currentChild, out openCurrentMenu._currentChild) && !CloseMenu (false)) {
+			return;
+		}
+		if (!openCurrentMenu.CheckSubMenu ()) {
+			return;
+		}
+		Application.GrabMouse (this);
+	}
+
+	// Activates the menu, handles either first focus, or activating an entry when it was already active
+	// For mouse events.
+	internal void Activate (int idx, int sIdx = -1, MenuBarItem subMenu = null)
+	{
+		_selected = idx;
+		_selectedSub = sIdx;
+		if (_openMenu == null) {
+			_previousFocused = SuperView == null ? Application.Current?.Focused ?? null : SuperView.Focused;
+		}
+
+		OpenMenu (idx, sIdx, subMenu);
+		SetNeedsDisplay ();
+	}
+
+	internal bool SelectEnabledItem (IEnumerable<MenuItem> chldren, int current, out int newCurrent, bool forward = true)
+	{
+		if (chldren == null) {
+			newCurrent = -1;
+			return true;
+		}
+
+		IEnumerable<MenuItem> childrens;
+		if (forward) {
+			childrens = chldren;
+		} else {
+			childrens = chldren.Reverse ();
+		}
+		int count;
+		if (forward) {
+			count = -1;
+		} else {
+			count = childrens.Count ();
+		}
+		foreach (var child in childrens) {
+			if (forward) {
+				if (++count < current) {
+					continue;
+				}
+			} else {
+				if (--count > current) {
+					continue;
+				}
+			}
+			if (child == null || !child.IsEnabled ()) {
+				if (forward) {
+					current++;
+				} else {
+					current--;
+				}
+			} else {
+				newCurrent = current;
+				return true;
+			}
+		}
+		newCurrent = -1;
+		return false;
+	}
+
+	/// <summary>
+	/// Closes the Menu programmatically if open and not canceled (as though F9 were pressed).
+	/// </summary>
+	public bool CloseMenu (bool ignoreUseSubMenusSingleFrame = false)
+	{
+		return CloseMenu (false, false, ignoreUseSubMenusSingleFrame);
+	}
+
+	bool _reopen;
+
+	internal bool CloseMenu (bool reopen = false, bool isSubMenu = false, bool ignoreUseSubMenusSingleFrame = false)
+	{
+		var mbi = isSubMenu ? openCurrentMenu._barItems : _openMenu?._barItems;
+		if (UseSubMenusSingleFrame && mbi != null &&
+		!ignoreUseSubMenusSingleFrame && mbi.Parent != null) {
+			return false;
+		}
+		_isMenuClosing = true;
+		_reopen = reopen;
+		var args = OnMenuClosing (mbi, reopen, isSubMenu);
+		if (args.Cancel) {
+			_isMenuClosing = false;
+			if (args.CurrentMenu.Parent != null) {
+				_openMenu._currentChild = ((MenuBarItem)args.CurrentMenu.Parent).Children.IndexOf (args.CurrentMenu);
+			}
+			return false;
+		}
+		switch (isSubMenu) {
+		case false:
+			if (_openMenu != null) {
+				Application.Current.Remove (_openMenu);
+			}
+			SetNeedsDisplay ();
+			if (_previousFocused != null && _previousFocused is Menu && _openMenu != null && _previousFocused.ToString () != openCurrentMenu.ToString ()) {
+				_previousFocused.SetFocus ();
+			}
+			_openMenu?.Dispose ();
+			_openMenu = null;
+			if (_lastFocused is Menu || _lastFocused is MenuBar) {
+				_lastFocused = null;
+			}
+			LastFocused = _lastFocused;
+			_lastFocused = null;
+			if (LastFocused != null && LastFocused.CanFocus) {
+				if (!reopen) {
+					_selected = -1;
+				}
+				if (_openSubMenu != null) {
+					_openSubMenu = null;
+				}
+				if (openCurrentMenu != null) {
+					Application.Current.Remove (openCurrentMenu);
+					openCurrentMenu.Dispose ();
+					openCurrentMenu = null;
+				}
+				LastFocused.SetFocus ();
+			} else if (_openSubMenu == null || _openSubMenu.Count == 0) {
+				CloseAllMenus ();
+			} else {
+				SetFocus ();
+				PositionCursor ();
+			}
+			IsMenuOpen = false;
+			break;
+
+		case true:
+			_selectedSub = -1;
+			SetNeedsDisplay ();
+			RemoveAllOpensSubMenus ();
+			openCurrentMenu._previousSubFocused.SetFocus ();
+			_openSubMenu = null;
+			IsMenuOpen = true;
+			break;
+		}
+		_reopen = false;
+		_isMenuClosing = false;
+		return true;
+	}
+
+	void RemoveSubMenu (int index, bool ignoreUseSubMenusSingleFrame = false)
+	{
+		if (_openSubMenu == null || UseSubMenusSingleFrame
+			&& !ignoreUseSubMenusSingleFrame && _openSubMenu.Count == 0) {
+			return;
+		}
+		for (int i = _openSubMenu.Count - 1; i > index; i--) {
+			_isMenuClosing = true;
+			Menu menu;
+			if (_openSubMenu.Count - 1 > 0) {
+				menu = _openSubMenu [i - 1];
+			} else {
+				menu = _openMenu;
+			}
+			if (!menu.Visible) {
+				menu.Visible = true;
+			}
+			openCurrentMenu = menu;
+			openCurrentMenu.SetFocus ();
+			if (_openSubMenu != null) {
+				menu = _openSubMenu [i];
+				Application.Current.Remove (menu);
+				_openSubMenu.Remove (menu);
+				menu.Dispose ();
+			}
+			RemoveSubMenu (i, ignoreUseSubMenusSingleFrame);
+		}
+		if (_openSubMenu.Count > 0) {
+			openCurrentMenu = _openSubMenu.Last ();
+		}
+
+		_isMenuClosing = false;
+	}
+
+	internal void RemoveAllOpensSubMenus ()
+	{
+		if (_openSubMenu != null) {
+			foreach (var item in _openSubMenu) {
+				Application.Current.Remove (item);
+				item.Dispose ();
+			}
+		}
+	}
+
+	internal void CloseAllMenus ()
+	{
+		if (!_isMenuOpening && !_isMenuClosing) {
+			if (_openSubMenu != null && !CloseMenu (false, true, true)) {
+				return;
+			}
+			if (!CloseMenu (false)) {
+				return;
+			}
+			if (LastFocused != null && LastFocused != this) {
+				_selected = -1;
+			}
+			Application.UngrabMouse ();
+		}
+		IsMenuOpen = false;
+		_openedByAltKey = false;
+		_openedByHotKey = false;
+		OnMenuAllClosed ();
+	}
+
+	internal void PreviousMenu (bool isSubMenu = false, bool ignoreUseSubMenusSingleFrame = false)
+	{
+		switch (isSubMenu) {
+		case false:
+			if (_selected <= 0) {
+				_selected = Menus.Length - 1;
+			} else {
+				_selected--;
+			}
+
+			if (_selected > -1 && !CloseMenu (true, false, ignoreUseSubMenusSingleFrame)) {
+				return;
+			}
+			OpenMenu (_selected);
+			if (!SelectEnabledItem (openCurrentMenu._barItems.Children, openCurrentMenu._currentChild, out openCurrentMenu._currentChild, false)) {
+				openCurrentMenu._currentChild = 0;
+			}
+			break;
+		case true:
+			if (_selectedSub > -1) {
+				_selectedSub--;
+				RemoveSubMenu (_selectedSub, ignoreUseSubMenusSingleFrame);
+				SetNeedsDisplay ();
+			} else {
+				PreviousMenu ();
+			}
+
+			break;
+		}
+	}
+
+	internal void NextMenu (bool isSubMenu = false, bool ignoreUseSubMenusSingleFrame = false)
+	{
+		switch (isSubMenu) {
+		case false:
+			if (_selected == -1) {
+				_selected = 0;
+			} else if (_selected + 1 == Menus.Length) {
+				_selected = 0;
+			} else {
+				_selected++;
+			}
+
+			if (_selected > -1 && !CloseMenu (true, ignoreUseSubMenusSingleFrame)) {
+				return;
+			}
+			OpenMenu (_selected);
+			SelectEnabledItem (openCurrentMenu._barItems.Children, openCurrentMenu._currentChild, out openCurrentMenu._currentChild);
+			break;
+		case true:
+			if (UseKeysUpDownAsKeysLeftRight) {
+				if (CloseMenu (false, true, ignoreUseSubMenusSingleFrame)) {
+					NextMenu (false, ignoreUseSubMenusSingleFrame);
+				}
+			} else {
+				var subMenu = openCurrentMenu._currentChild > -1 && openCurrentMenu._barItems.Children.Length > 0
+					? openCurrentMenu._barItems.SubMenu (openCurrentMenu._barItems.Children [openCurrentMenu._currentChild])
+					: null;
+				if ((_selectedSub == -1 || _openSubMenu == null || _openSubMenu?.Count - 1 == _selectedSub) && subMenu == null) {
+					if (_openSubMenu != null && !CloseMenu (false, true)) {
+						return;
+					}
+					NextMenu (false, ignoreUseSubMenusSingleFrame);
+				} else if (subMenu != null || openCurrentMenu._currentChild > -1
+					&& !openCurrentMenu._barItems.Children [openCurrentMenu._currentChild].IsFromSubMenu) {
+					_selectedSub++;
+					openCurrentMenu.CheckSubMenu ();
+				} else {
+					if (CloseMenu (false, true, ignoreUseSubMenusSingleFrame)) {
+						NextMenu (false, ignoreUseSubMenusSingleFrame);
+					}
+					return;
+				}
+
+				SetNeedsDisplay ();
+				if (UseKeysUpDownAsKeysLeftRight) {
+					openCurrentMenu.CheckSubMenu ();
+				}
+			}
+			break;
+		}
+	}
+
+	void ProcessMenu (int i, MenuBarItem mi)
+	{
+		if (_selected < 0 && IsMenuOpen) {
+			return;
+		}
+
+		if (mi.IsTopLevel) {
+			BoundsToScreen (i, 0, out int rx, out int ry);
+			var menu = new Menu (this, rx, ry, mi, null, MenusBorderStyle);
+			menu.Run (mi.Action);
+			menu.Dispose ();
+		} else {
+			Application.GrabMouse (this);
+			_selected = i;
+			OpenMenu (i);
+			if (!SelectEnabledItem (openCurrentMenu._barItems.Children, openCurrentMenu._currentChild, out openCurrentMenu._currentChild) && !CloseMenu (false)) {
+				return;
+			}
+			if (!openCurrentMenu.CheckSubMenu ()) {
+				return;
+			}
+		}
+		SetNeedsDisplay ();
+	}
+
+
+	void CloseMenuBar ()
+	{
+		if (!CloseMenu (false)) {
+			return;
+		}
+		if (_openedByAltKey) {
+			_openedByAltKey = false;
+			LastFocused?.SetFocus ();
+		}
+		SetNeedsDisplay ();
+	}
+
+	void MoveRight ()
+	{
+		_selected = (_selected + 1) % Menus.Length;
+		OpenMenu (_selected);
+		SetNeedsDisplay ();
+	}
+
+	void MoveLeft ()
+	{
+		_selected--;
+		if (_selected < 0) {
+			_selected = Menus.Length - 1;
+		}
+		OpenMenu (_selected);
+		SetNeedsDisplay ();
+	}
+
+	#region Mouse Handling
+	///<inheritdoc/>
+	public override bool OnEnter (View view)
+	{
+		Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
+
+		return base.OnEnter (view);
+	}
+
+	///<inheritdoc/>
+	public override bool OnLeave (View view)
+	{
+		if ((!(view is MenuBar) && !(view is Menu) || !(view is MenuBar) && !(view is Menu) && _openMenu != null) && !_isCleaning && !_reopen) {
+			CleanUp ();
+		}
+		return base.OnLeave (view);
+	}
+
+	///<inheritdoc/>
+	public override bool MouseEvent (MouseEvent me)
+	{
+		if (!_handled && !HandleGrabView (me, this)) {
+			return false;
+		}
+		_handled = false;
+
+		if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked || me.Flags == MouseFlags.Button1TripleClicked || me.Flags == MouseFlags.Button1Clicked ||
+		me.Flags == MouseFlags.ReportMousePosition && _selected > -1 ||
+		me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && _selected > -1) {
+			int pos = _xOrigin;
+			Point locationOffset = default;
+			if (SuperView != null) {
+				locationOffset.X += SuperView.Border.Thickness.Left;
+				locationOffset.Y += SuperView.Border.Thickness.Top;
+			}
+			int cx = me.X - locationOffset.X;
+			for (int i = 0; i < Menus.Length; i++) {
+				if (cx >= pos && cx < pos + _leftPadding + Menus [i].TitleLength + Menus [i].Help.GetColumns () + _rightPadding) {
+					if (me.Flags == MouseFlags.Button1Clicked) {
+						if (Menus [i].IsTopLevel) {
+							BoundsToScreen (i, 0, out int rx, out int ry);
+							var menu = new Menu (this, rx, ry, Menus [i], null, MenusBorderStyle);
+							menu.Run (Menus [i].Action);
+							menu.Dispose ();
+						} else if (!IsMenuOpen) {
+							Activate (i);
+						}
+					} else if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked || me.Flags == MouseFlags.Button1TripleClicked) {
+						if (IsMenuOpen && !Menus [i].IsTopLevel) {
+							CloseAllMenus ();
+						} else if (!Menus [i].IsTopLevel) {
+							Activate (i);
+						}
+					} else if (_selected != i && _selected > -1 && (me.Flags == MouseFlags.ReportMousePosition ||
+											me.Flags == MouseFlags.Button1Pressed && me.Flags == MouseFlags.ReportMousePosition)) {
+						if (IsMenuOpen) {
+							if (!CloseMenu (true, false)) {
+								return true;
+							}
+							Activate (i);
+						}
+					} else if (IsMenuOpen) {
+						if (!UseSubMenusSingleFrame || UseSubMenusSingleFrame && openCurrentMenu != null
+													&& openCurrentMenu._barItems.Parent != null && openCurrentMenu._barItems.Parent.Parent != Menus [i]) {
+
+							Activate (i);
+						}
+					}
+					return true;
+				} else if (i == Menus.Length - 1 && me.Flags == MouseFlags.Button1Clicked) {
+					if (IsMenuOpen && !Menus [i].IsTopLevel) {
+						CloseAllMenus ();
+						return true;
+					}
+				}
+				pos += _leftPadding + Menus [i].TitleLength + _rightPadding;
+			}
+		}
+		return false;
+	}
+
+	internal bool _handled;
+	internal bool _isContextMenuLoading;
+
+	internal bool HandleGrabView (MouseEvent me, View current)
+	{
+		if (Application.MouseGrabView != null) {
+			if (me.View is MenuBar || me.View is Menu) {
+				var mbar = GetMouseGrabViewInstance (me.View);
+				if (mbar != null) {
+					if (me.Flags == MouseFlags.Button1Clicked) {
+						mbar.CleanUp ();
+						Application.GrabMouse (me.View);
+					} else {
+						_handled = false;
+						return false;
+					}
+				}
+				if (me.View != current) {
+					Application.UngrabMouse ();
+					var v = me.View;
+					Application.GrabMouse (v);
+					MouseEvent nme;
+					if (me.Y > -1) {
+						var newxy = v.ScreenToFrame (me.X, me.Y);
+						nme = new MouseEvent () {
+							X = newxy.X,
+							Y = newxy.Y,
+							Flags = me.Flags,
+							OfX = me.X - newxy.X,
+							OfY = me.Y - newxy.Y,
+							View = v
+						};
+					} else {
+						nme = new MouseEvent () {
+							X = me.X + current.Frame.X,
+							Y = 0,
+							Flags = me.Flags,
+							View = v
+						};
+					}
+
+					v.MouseEvent (nme);
+					return false;
+				}
+			} else if (!_isContextMenuLoading && !(me.View is MenuBar || me.View is Menu)
+							&& me.Flags != MouseFlags.ReportMousePosition && me.Flags != 0) {
+
+				Application.UngrabMouse ();
+				if (IsMenuOpen) {
+					CloseAllMenus ();
+				}
+				_handled = false;
+				return false;
+			} else {
+				_handled = false;
+				_isContextMenuLoading = false;
+				return false;
+			}
+		} else if (!IsMenuOpen && (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked
+										|| me.Flags == MouseFlags.Button1TripleClicked || me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))) {
+
+			Application.GrabMouse (current);
+		} else if (IsMenuOpen && (me.View is MenuBar || me.View is Menu)) {
+			Application.GrabMouse (me.View);
+		} else {
+			_handled = false;
+			return false;
+		}
+
+		_handled = true;
+
+		return true;
+	}
+
+	MenuBar GetMouseGrabViewInstance (View view)
+	{
+		if (view == null || Application.MouseGrabView == null) {
+			return null;
+		}
+
+		MenuBar hostView = null;
+		if (view is MenuBar) {
+			hostView = (MenuBar)view;
+		} else if (view is Menu) {
+			hostView = ((Menu)view)._host;
+		}
+
+		var grabView = Application.MouseGrabView;
+		MenuBar hostGrabView = null;
+		if (grabView is MenuBar) {
+			hostGrabView = (MenuBar)grabView;
+		} else if (grabView is Menu) {
+			hostGrabView = ((Menu)grabView)._host;
+		}
+
+		return hostView != hostGrabView ? hostGrabView : null;
+	}
+	#endregion Mouse Handling
+
+	/// <summary>
+	/// Gets the superview location offset relative to the <see cref="ConsoleDriver"/> location.
+	/// </summary>
+	/// <returns>The location offset.</returns>
+	internal Point GetScreenOffset ()
+	{
+		if (Driver == null) {
+			return Point.Empty;
+		}
+		var superViewFrame = SuperView == null ? new Rect (0, 0, Driver.Cols, Driver.Rows) : SuperView.Frame;
+		var sv = SuperView == null ? Application.Current : SuperView;
+		var boundsOffset = sv.GetBoundsOffset ();
+		return new Point (superViewFrame.X - sv.Frame.X - boundsOffset.X,
+			superViewFrame.Y - sv.Frame.Y - boundsOffset.Y);
+	}
+
+	/// <summary>
+	/// Gets the <see cref="Application.Current"/> location offset relative to the <see cref="ConsoleDriver"/> location.
+	/// </summary>
+	/// <returns>The location offset.</returns>
+	internal Point GetScreenOffsetFromCurrent ()
+	{
+		var screen = new Rect (0, 0, Driver.Cols, Driver.Rows);
+		var currentFrame = Application.Current.Frame;
+		var boundsOffset = Application.Top.GetBoundsOffset ();
+		return new Point (screen.X - currentFrame.X - boundsOffset.X
+			, screen.Y - currentFrame.Y - boundsOffset.Y);
+	}
+}

+ 97 - 0
Terminal.Gui/Views/Menu/MenuEventArgs.cs

@@ -0,0 +1,97 @@
+using System;
+
+namespace Terminal.Gui;
+/// <summary>
+/// An <see cref="EventArgs"/> which allows passing a cancelable menu opening event or replacing with a new <see cref="MenuBarItem"/>.
+/// </summary>
+public class MenuOpeningEventArgs : EventArgs {
+	/// <summary>
+	/// The current <see cref="MenuBarItem"/> parent.
+	/// </summary>
+	public MenuBarItem CurrentMenu { get; }
+
+	/// <summary>
+	/// The new <see cref="MenuBarItem"/> to be replaced.
+	/// </summary>
+	public MenuBarItem NewMenuBarItem { get; set; }
+	/// <summary>
+	/// Flag that allows the cancellation of the event. If set to <see langword="true"/> in the
+	/// event handler, the event will be canceled. 
+	/// </summary>
+	public bool Cancel { get; set; }
+
+	/// <summary>
+	/// Initializes a new instance of <see cref="MenuOpeningEventArgs"/>.
+	/// </summary>
+	/// <param name="currentMenu">The current <see cref="MenuBarItem"/> parent.</param>
+	public MenuOpeningEventArgs (MenuBarItem currentMenu)
+	{
+		CurrentMenu = currentMenu;
+	}
+}
+
+/// <summary>
+/// Defines arguments for the <see cref="MenuBar.MenuOpened"/> event
+/// </summary>
+public class MenuOpenedEventArgs : EventArgs {
+	/// <summary>
+	/// Creates a new instance of the <see cref="MenuOpenedEventArgs"/> class
+	/// </summary>
+	/// <param name="parent"></param>
+	/// <param name="menuItem"></param>
+	public MenuOpenedEventArgs (MenuBarItem parent, MenuItem menuItem)
+	{
+		Parent = parent;
+		MenuItem = menuItem;
+	}
+
+	/// <summary>
+	/// The parent of <see cref="MenuItem"/>. Will be null if menu opening
+	/// is the root.
+	/// </summary>
+	public MenuBarItem Parent { get; }
+
+	/// <summary>
+	/// Gets the <see cref="MenuItem"/> being opened.
+	/// </summary>
+	public MenuItem MenuItem { get; }
+}
+
+/// <summary>
+/// An <see cref="EventArgs"/> which allows passing a cancelable menu closing event.
+/// </summary>
+public class MenuClosingEventArgs : EventArgs {
+	/// <summary>
+	/// The current <see cref="MenuBarItem"/> parent.
+	/// </summary>
+	public MenuBarItem CurrentMenu { get; }
+
+	/// <summary>
+	/// Indicates whether the current menu will reopen.
+	/// </summary>
+	public bool Reopen { get; }
+
+	/// <summary>
+	/// Indicates whether the current menu is a sub-menu.
+	/// </summary>
+	public bool IsSubMenu { get; }
+
+	/// <summary>
+	/// Flag that allows the cancellation of the event. If set to <see langword="true"/> in the
+	/// event handler, the event will be canceled. 
+	/// </summary>
+	public bool Cancel { get; set; }
+
+	/// <summary>
+	/// Initializes a new instance of <see cref="MenuClosingEventArgs"/>.
+	/// </summary>
+	/// <param name="currentMenu">The current <see cref="MenuBarItem"/> parent.</param>
+	/// <param name="reopen">Whether the current menu will reopen.</param>
+	/// <param name="isSubMenu">Indicates whether it is a sub-menu.</param>
+	public MenuClosingEventArgs (MenuBarItem currentMenu, bool reopen, bool isSubMenu)
+	{
+		CurrentMenu = currentMenu;
+		Reopen = reopen;
+		IsSubMenu = isSubMenu;
+	}
+}

+ 0 - 98
Terminal.Gui/Views/MenuEventArgs.cs

@@ -1,98 +0,0 @@
-using System;
-
-namespace Terminal.Gui {
-	/// <summary>
-	/// An <see cref="EventArgs"/> which allows passing a cancelable menu opening event or replacing with a new <see cref="MenuBarItem"/>.
-	/// </summary>
-	public class MenuOpeningEventArgs : EventArgs {
-		/// <summary>
-		/// The current <see cref="MenuBarItem"/> parent.
-		/// </summary>
-		public MenuBarItem CurrentMenu { get; }
-
-		/// <summary>
-		/// The new <see cref="MenuBarItem"/> to be replaced.
-		/// </summary>
-		public MenuBarItem NewMenuBarItem { get; set; }
-		/// <summary>
-		/// Flag that allows the cancellation of the event. If set to <see langword="true"/> in the
-		/// event handler, the event will be canceled. 
-		/// </summary>
-		public bool Cancel { get; set; }
-
-		/// <summary>
-		/// Initializes a new instance of <see cref="MenuOpeningEventArgs"/>.
-		/// </summary>
-		/// <param name="currentMenu">The current <see cref="MenuBarItem"/> parent.</param>
-		public MenuOpeningEventArgs (MenuBarItem currentMenu)
-		{
-			CurrentMenu = currentMenu;
-		}
-	}
-
-	/// <summary>
-	/// Defines arguments for the <see cref="MenuBar.MenuOpened"/> event
-	/// </summary>
-	public class MenuOpenedEventArgs : EventArgs {
-		/// <summary>
-		/// Creates a new instance of the <see cref="MenuOpenedEventArgs"/> class
-		/// </summary>
-		/// <param name="parent"></param>
-		/// <param name="menuItem"></param>
-		public MenuOpenedEventArgs (MenuBarItem parent, MenuItem menuItem)
-		{
-			Parent = parent;
-			MenuItem = menuItem;
-		}
-
-		/// <summary>
-		/// The parent of <see cref="MenuItem"/>.  Will be null if menu opening
-		/// is the root (see <see cref="MenuBarItem.IsTopLevel"/>).
-		/// </summary>
-		public MenuBarItem Parent { get; }
-
-		/// <summary>
-		/// Gets the <see cref="MenuItem"/> being opened.
-		/// </summary>
-		public MenuItem MenuItem { get; }
-	}
-
-	/// <summary>
-	/// An <see cref="EventArgs"/> which allows passing a cancelable menu closing event.
-	/// </summary>
-	public class MenuClosingEventArgs : EventArgs {
-		/// <summary>
-		/// The current <see cref="MenuBarItem"/> parent.
-		/// </summary>
-		public MenuBarItem CurrentMenu { get; }
-
-		/// <summary>
-		/// Indicates whether the current menu will reopen.
-		/// </summary>
-		public bool Reopen { get; }
-
-		/// <summary>
-		/// Indicates whether the current menu is a sub-menu.
-		/// </summary>
-		public bool IsSubMenu { get; }
-
-		/// <summary>
-		/// Flag that allows the cancellation of the event. If set to <see langword="true"/> in the
-		/// event handler, the event will be canceled. 
-		/// </summary>
-		public bool Cancel { get; set; }
-
-		/// <summary>
-		/// Initializes a new instance of <see cref="MenuClosingEventArgs"/>.
-		/// </summary>
-		/// <param name="currentMenu">The current <see cref="MenuBarItem"/> parent.</param>
-		/// <param name="reopen">Whether the current menu will reopen.</param>
-		/// <param name="isSubMenu">Indicates whether it is a sub-menu.</param>
-		public MenuClosingEventArgs (MenuBarItem currentMenu, bool reopen, bool isSubMenu)
-		{
-			CurrentMenu = currentMenu;
-			Reopen = reopen;
-			IsSubMenu = isSubMenu;
-		}
-	}
-}

+ 339 - 344
Terminal.Gui/Views/RadioGroup.cs

@@ -3,414 +3,409 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui;
+/// <summary>
+/// Displays a group of labels each with a selected indicator. Only one of those can be selected at a given time.
+/// </summary>
+public class RadioGroup : View {
+	int _selected = -1;
+	int _cursor;
+	DisplayModeLayout _displayMode;
+	int _horizontalSpace = 2;
+	List<(int pos, int length)> _horizontal;
+
 	/// <summary>
-	/// Displays a group of labels each with a selected indicator. Only one of those can be selected at a given time.
+	/// Initializes a new instance of the <see cref="RadioGroup"/> class using <see cref="LayoutStyle.Computed"/> layout.
 	/// </summary>
-	public class RadioGroup : View {
-		int selected = -1;
-		int cursor;
-		DisplayModeLayout displayMode;
-		int horizontalSpace = 2;
-		List<(int pos, int length)> horizontal;
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="RadioGroup"/> class using <see cref="LayoutStyle.Computed"/> layout.
-		/// </summary>
-		public RadioGroup () : this (radioLabels: new string [] { }) { }
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="RadioGroup"/> class using <see cref="LayoutStyle.Computed"/> layout.
-		/// </summary>
-		/// <param name="radioLabels">The radio labels; an array of strings that can contain hotkeys using an underscore before the letter.</param>
-		/// <param name="selected">The index of the item to be selected, the value is clamped to the number of items.</param>
-		public RadioGroup (string [] radioLabels, int selected = 0) : base ()
-		{
-			SetInitalProperties (Rect.Empty, radioLabels, selected);
-		}
+	public RadioGroup () : this (radioLabels: new string [] { }) { }
 
-		/// <summary>
-		/// Initializes a new instance of the <see cref="RadioGroup"/> class using <see cref="LayoutStyle.Absolute"/> layout.
-		/// </summary>
-		/// <param name="rect">Boundaries for the radio group.</param>
-		/// <param name="radioLabels">The radio labels; an array of strings that can contain hotkeys using an underscore before the letter.</param>
-		/// <param name="selected">The index of item to be selected, the value is clamped to the number of items.</param>
-		public RadioGroup (Rect rect, string [] radioLabels, int selected = 0) : base (rect)
-		{
-			SetInitalProperties (rect, radioLabels, selected);
-		}
+	/// <summary>
+	/// Initializes a new instance of the <see cref="RadioGroup"/> class using <see cref="LayoutStyle.Computed"/> layout.
+	/// </summary>
+	/// <param name="radioLabels">The radio labels; an array of strings that can contain hotkeys using an underscore before the letter.</param>
+	/// <param name="selected">The index of the item to be selected, the value is clamped to the number of items.</param>
+	public RadioGroup (string [] radioLabels, int selected = 0) : base ()
+	{
+		SetInitialProperties (Rect.Empty, radioLabels, selected);
+	}
 
-		/// <summary>
-		/// Initializes a new instance of the <see cref="RadioGroup"/> class using <see cref="LayoutStyle.Absolute"/> layout.
-		/// The <see cref="View"/> frame is computed from the provided radio labels.
-		/// </summary>
-		/// <param name="x">The x coordinate.</param>
-		/// <param name="y">The y coordinate.</param>
-		/// <param name="radioLabels">The radio labels; an array of strings that can contain hotkeys using an underscore before the letter.</param>
-		/// <param name="selected">The item to be selected, the value is clamped to the number of items.</param>
-		public RadioGroup (int x, int y, string [] radioLabels, int selected = 0) :
-			this (MakeRect (x, y, radioLabels != null ? radioLabels.ToList () : null), radioLabels, selected)
-		{ }
-
-		void SetInitalProperties (Rect rect, string [] radioLabels, int selected)
-		{
-			if (radioLabels == null) {
-				this.radioLabels = new List<string> ();
-			} else {
-				this.radioLabels = radioLabels.ToList ();
-			}
+	/// <summary>
+	/// Initializes a new instance of the <see cref="RadioGroup"/> class using <see cref="LayoutStyle.Absolute"/> layout.
+	/// </summary>
+	/// <param name="rect">Boundaries for the radio group.</param>
+	/// <param name="radioLabels">The radio labels; an array of strings that can contain hotkeys using an underscore before the letter.</param>
+	/// <param name="selected">The index of item to be selected, the value is clamped to the number of items.</param>
+	public RadioGroup (Rect rect, string [] radioLabels, int selected = 0) : base (rect)
+	{
+		SetInitialProperties (rect, radioLabels, selected);
+	}
 
-			this.selected = selected;
-			Frame = rect;
-			CanFocus = true;
-			HotKeySpecifier = new Rune ('_');
-
-			// Things this view knows how to do
-			AddCommand (Command.LineUp, () => { MoveUp (); return true; });
-			AddCommand (Command.LineDown, () => { MoveDown (); return true; });
-			AddCommand (Command.TopHome, () => { MoveHome (); return true; });
-			AddCommand (Command.BottomEnd, () => { MoveEnd (); return true; });
-			AddCommand (Command.Accept, () => { SelectItem (); return true; });
-
-			// Default keybindings for this view
-			AddKeyBinding (Key.CursorUp, Command.LineUp);
-			AddKeyBinding (Key.CursorDown, Command.LineDown);
-			AddKeyBinding (Key.Home, Command.TopHome);
-			AddKeyBinding (Key.End, Command.BottomEnd);
-			AddKeyBinding (Key.Space, Command.Accept);
-
-			LayoutStarted += RadioGroup_LayoutStarted;
+	/// <summary>
+	/// Initializes a new instance of the <see cref="RadioGroup"/> class using <see cref="LayoutStyle.Absolute"/> layout.
+	/// The <see cref="View"/> frame is computed from the provided radio labels.
+	/// </summary>
+	/// <param name="x">The x coordinate.</param>
+	/// <param name="y">The y coordinate.</param>
+	/// <param name="radioLabels">The radio labels; an array of strings that can contain hotkeys using an underscore before the letter.</param>
+	/// <param name="selected">The item to be selected, the value is clamped to the number of items.</param>
+	public RadioGroup (int x, int y, string [] radioLabels, int selected = 0) :
+		this (MakeRect (x, y, radioLabels != null ? radioLabels.ToList () : null), radioLabels, selected)
+	{ }
+
+	void SetInitialProperties (Rect rect, string [] radioLabels, int selected)
+	{
+		HotKeySpecifier = new Rune ('_');
+
+		if (radioLabels != null) {
+			RadioLabels = radioLabels;
 		}
 
-		private void RadioGroup_LayoutStarted (object sender, EventArgs e)
-		{
-			SetWidthHeight (radioLabels);
-		}
+		_selected = selected;
+		Frame = rect;
+		CanFocus = true;
+
+		// Things this view knows how to do
+		AddCommand (Command.LineUp, () => { MoveUp (); return true; });
+		AddCommand (Command.LineDown, () => { MoveDown (); return true; });
+		AddCommand (Command.TopHome, () => { MoveHome (); return true; });
+		AddCommand (Command.BottomEnd, () => { MoveEnd (); return true; });
+		AddCommand (Command.Accept, () => { SelectItem (); return true; });
+
+		// Default keybindings for this view
+		KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
+		KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
+		KeyBindings.Add (KeyCode.Home, Command.TopHome);
+		KeyBindings.Add (KeyCode.End, Command.BottomEnd);
+		KeyBindings.Add (KeyCode.Space, Command.Accept);
+
+		LayoutStarted += RadioGroup_LayoutStarted;
+	}
 
-		/// <summary>
-		/// Gets or sets the <see cref="DisplayModeLayout"/> for this <see cref="RadioGroup"/>.
-		/// </summary>
-		public DisplayModeLayout DisplayMode {
-			get { return displayMode; }
-			set {
-				if (displayMode != value) {
-					displayMode = value;
-					SetWidthHeight (radioLabels);
-					SetNeedsDisplay ();
-				}
+	void RadioGroup_LayoutStarted (object sender, EventArgs e)
+	{
+		SetWidthHeight (_radioLabels);
+	}
+
+	/// <summary>
+	/// Gets or sets the <see cref="DisplayModeLayout"/> for this <see cref="RadioGroup"/>.
+	/// </summary>
+	public DisplayModeLayout DisplayMode {
+		get { return _displayMode; }
+		set {
+			if (_displayMode != value) {
+				_displayMode = value;
+				SetWidthHeight (_radioLabels);
+				SetNeedsDisplay ();
 			}
 		}
+	}
 
-		/// <summary>
-		/// Gets or sets the horizontal space for this <see cref="RadioGroup"/> if the <see cref="DisplayMode"/> is <see cref="DisplayModeLayout.Horizontal"/>
-		/// </summary>
-		public int HorizontalSpace {
-			get { return horizontalSpace; }
-			set {
-				if (horizontalSpace != value && displayMode == DisplayModeLayout.Horizontal) {
-					horizontalSpace = value;
-					SetWidthHeight (radioLabels);
-					UpdateTextFormatterText ();
-					SetNeedsDisplay ();
-				}
+	/// <summary>
+	/// Gets or sets the horizontal space for this <see cref="RadioGroup"/> if the <see cref="DisplayMode"/> is <see cref="DisplayModeLayout.Horizontal"/>
+	/// </summary>
+	public int HorizontalSpace {
+		get { return _horizontalSpace; }
+		set {
+			if (_horizontalSpace != value && _displayMode == DisplayModeLayout.Horizontal) {
+				_horizontalSpace = value;
+				SetWidthHeight (_radioLabels);
+				UpdateTextFormatterText ();
+				SetNeedsDisplay ();
 			}
 		}
+	}
 
-		void SetWidthHeight (List<string> radioLabels)
-		{
-			switch (displayMode) {
-			case DisplayModeLayout.Vertical:
-				var r = MakeRect (0, 0, radioLabels);
-				Bounds = new Rect (Bounds.Location, new Size (r.Width, radioLabels.Count));
-				break;
-
-			case DisplayModeLayout.Horizontal:
-				CalculateHorizontalPositions ();
-				var length = 0;
-				foreach (var item in horizontal) {
-					length += item.length;
-				}
-				var hr = new Rect (0, 0, length, 1);
-				if (IsAdded && LayoutStyle == LayoutStyle.Computed) {
-					Width = hr.Width;
-					Height = 1;
-				} else {
-					Bounds = new Rect (Bounds.Location, new Size (hr.Width, radioLabels.Count));
-				}
-				break;
+	void SetWidthHeight (List<string> radioLabels)
+	{
+		switch (_displayMode) {
+		case DisplayModeLayout.Vertical:
+			var r = MakeRect (0, 0, radioLabels);
+			Bounds = new Rect (Bounds.Location, new Size (r.Width, radioLabels.Count));
+			break;
+
+		case DisplayModeLayout.Horizontal:
+			CalculateHorizontalPositions ();
+			var length = 0;
+			foreach (var item in _horizontal) {
+				length += item.length;
+			}
+			var hr = new Rect (0, 0, length, 1);
+			if (IsAdded && LayoutStyle == LayoutStyle.Computed) {
+				Width = hr.Width;
+				Height = 1;
+			} else {
+				Bounds = new Rect (Bounds.Location, new Size (hr.Width, radioLabels.Count));
 			}
+			break;
 		}
+	}
 
-		static Rect MakeRect (int x, int y, List<string> radioLabels)
-		{
-			if (radioLabels == null) {
-				return new Rect (x, y, 0, 0);
-			}
+	static Rect MakeRect (int x, int y, List<string> radioLabels)
+	{
+		if (radioLabels == null) {
+			return new Rect (x, y, 0, 0);
+		}
 
-			int width = 0;
+		int width = 0;
 
-			foreach (var s in radioLabels) {
-				width = Math.Max (s.GetColumns () + 2, width);
-			}
-			return new Rect (x, y, width, radioLabels.Count);
+		foreach (var s in radioLabels) {
+			width = Math.Max (s.GetColumns () + 2, width);
 		}
+		return new Rect (x, y, width, radioLabels.Count);
+	}
 
-		List<string> radioLabels = new List<string> ();
-
-		/// <summary>
-		/// The radio labels to display
-		/// </summary>
-		/// <value>The radio labels.</value>
-		public string [] RadioLabels {
-			get => radioLabels.ToArray ();
-			set {
-				var prevCount = radioLabels.Count;
-				radioLabels = value.ToList ();
-				if (prevCount != radioLabels.Count) {
-					SetWidthHeight (radioLabels);
+	List<string> _radioLabels = new List<string> ();
+
+	/// <summary>
+	/// The radio labels to display. A key binding will be added for each radio radio 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 (var label in _radioLabels) {
+				if (TextFormatter.FindHotKey (label, HotKeySpecifier, true, out _, out var hotKey)) {
+					AddKeyBindingsForHotKey (hotKey, KeyCode.Null);
 				}
-				SelectedItem = 0;
-				cursor = 0;
-				SetNeedsDisplay ();
 			}
-		}
-
-		private void CalculateHorizontalPositions ()
-		{
-			if (displayMode == DisplayModeLayout.Horizontal) {
-				horizontal = new List<(int pos, int length)> ();
-				int start = 0;
-				int length = 0;
-				for (int i = 0; i < radioLabels.Count; i++) {
-					start += length;
-					length = radioLabels [i].GetColumns () + 2 + (i < radioLabels.Count - 1 ? horizontalSpace : 0);
-					horizontal.Add ((start, length));
+			var prevCount = _radioLabels.Count;
+			_radioLabels = value.ToList ();
+			foreach (var label in _radioLabels) {
+				if (TextFormatter.FindHotKey (label, HotKeySpecifier, true, out _, out var hotKey)) {
+					AddKeyBindingsForHotKey (KeyCode.Null, hotKey);
 				}
 			}
+			if (prevCount != _radioLabels.Count) {
+				SetWidthHeight (_radioLabels);
+			}
+			SelectedItem = 0;
+			_cursor = 0;
+			SetNeedsDisplay ();
 		}
+	}
 
-		///<inheritdoc/>
-		public override void OnDrawContent (Rect contentArea)
-		{
-			base.OnDrawContent (contentArea);
-
-			Driver.SetAttribute (GetNormalColor ());
-			for (int i = 0; i < radioLabels.Count; i++) {
-				switch (DisplayMode) {
-				case DisplayModeLayout.Vertical:
-					Move (0, i);
-					break;
-				case DisplayModeLayout.Horizontal:
-					Move (horizontal [i].pos, 0);
+	/// <inheritdoc/>
+	public override bool? OnInvokingKeyBindings (Key keyEvent)
+	{
+		// This is a bit of a hack. We want to handle the key bindings for the radio group but
+		// InvokeKeyBindings doesn't pass any context so we can't tell if the key binding is for
+		// the radio group or for one of the radio buttons. So before we call the base class
+		// we set SelectedItem appropriately.
+
+		var key = keyEvent;
+		if (KeyBindings.TryGet (key, out _)) {
+			// Search RadioLabels 
+			for (int i = 0; i < _radioLabels.Count; i++) {
+				if (TextFormatter.FindHotKey (_radioLabels [i], HotKeySpecifier, true, out _, out var hotKey) 
+					&& (key.NoAlt.NoCtrl.NoShift) == hotKey) {
+					SelectedItem = i;
+					keyEvent.Scope = KeyBindingScope.HotKey;
 					break;
 				}
-				var rl = radioLabels [i];
-				Driver.SetAttribute (GetNormalColor ());
-				Driver.AddStr ($"{(i == selected ? CM.Glyphs.Selected : CM.Glyphs.UnSelected)} ");
-				TextFormatter.FindHotKey (rl, HotKeySpecifier, true, out int hotPos, out Key hotKey);
-				if (hotPos != -1 && (hotKey != Key.Null || hotKey != Key.Unknown)) {
-					var rlRunes = rl.ToRunes ();
-					for (int j = 0; j < rlRunes.Length; j++) {
-						Rune rune = rlRunes [j];
-						if (j == hotPos && i == cursor) {
-							Application.Driver.SetAttribute (HasFocus ? ColorScheme.HotFocus : GetHotNormalColor ());
-						} else if (j == hotPos && i != cursor) {
-							Application.Driver.SetAttribute (GetHotNormalColor ());
-						} else if (HasFocus && i == cursor) {
-							Application.Driver.SetAttribute (ColorScheme.Focus);
-						}
-						if (rune == HotKeySpecifier && j + 1 < rlRunes.Length) {
-							j++;
-							rune = rlRunes [j];
-							if (i == cursor) {
-								Application.Driver.SetAttribute (HasFocus ? ColorScheme.HotFocus : GetHotNormalColor ());
-							} else if (i != cursor) {
-								Application.Driver.SetAttribute (GetHotNormalColor ());
-							}
-						}
-						Application.Driver.AddRune (rune);
-						Driver.SetAttribute (GetNormalColor ());
-					}
-				} else {
-					DrawHotString (rl, HasFocus && i == cursor, ColorScheme);
-				}
+			}
+
+		}
+		return base.OnInvokingKeyBindings (keyEvent);
+	}
+
+	void CalculateHorizontalPositions ()
+	{
+		if (_displayMode == DisplayModeLayout.Horizontal) {
+			_horizontal = new List<(int pos, int length)> ();
+			int start = 0;
+			int length = 0;
+			for (int i = 0; i < _radioLabels.Count; i++) {
+				start += length;
+				length = _radioLabels [i].GetColumns () + 2 + (i < _radioLabels.Count - 1 ? _horizontalSpace : 0);
+				_horizontal.Add ((start, length));
 			}
 		}
+	}
 
-		///<inheritdoc/>
-		public override void PositionCursor ()
-		{
+	///<inheritdoc/>
+	public override void OnDrawContent (Rect contentArea)
+	{
+		base.OnDrawContent (contentArea);
+
+		Driver.SetAttribute (GetNormalColor ());
+		for (int i = 0; i < _radioLabels.Count; i++) {
 			switch (DisplayMode) {
 			case DisplayModeLayout.Vertical:
-				Move (0, cursor);
+				Move (0, i);
 				break;
 			case DisplayModeLayout.Horizontal:
-				Move (horizontal [cursor].pos, 0);
+				Move (_horizontal [i].pos, 0);
 				break;
 			}
-		}
-
-		/// <summary>
-		/// Invoked when the selected radio label has changed.
-		/// </summary>
-		public event EventHandler<SelectedItemChangedArgs> SelectedItemChanged;
-
-		/// <summary>
-		/// The currently selected item from the list of radio labels
-		/// </summary>
-		/// <value>The selected.</value>
-		public int SelectedItem {
-			get => selected;
-			set {
-				OnSelectedItemChanged (value, SelectedItem);
-				cursor = selected;
-				SetNeedsDisplay ();
+			var rl = _radioLabels [i];
+			Driver.SetAttribute (GetNormalColor ());
+			Driver.AddStr ($"{(i == _selected ? CM.Glyphs.Selected : CM.Glyphs.UnSelected)} ");
+			TextFormatter.FindHotKey (rl, HotKeySpecifier, true, out int hotPos, out var hotKey);
+			if (hotPos != -1 && (hotKey != KeyCode.Null || hotKey != KeyCode.Unknown)) {
+				var rlRunes = rl.ToRunes ();
+				for (int j = 0; j < rlRunes.Length; j++) {
+					Rune rune = rlRunes [j];
+					if (j == hotPos && i == _cursor) {
+						Application.Driver.SetAttribute (HasFocus ? ColorScheme.HotFocus : GetHotNormalColor ());
+					} else if (j == hotPos && i != _cursor) {
+						Application.Driver.SetAttribute (GetHotNormalColor ());
+					} else if (HasFocus && i == _cursor) {
+						Application.Driver.SetAttribute (ColorScheme.Focus);
+					}
+					if (rune == HotKeySpecifier && j + 1 < rlRunes.Length) {
+						j++;
+						rune = rlRunes [j];
+						if (i == _cursor) {
+							Application.Driver.SetAttribute (HasFocus ? ColorScheme.HotFocus : GetHotNormalColor ());
+						} else if (i != _cursor) {
+							Application.Driver.SetAttribute (GetHotNormalColor ());
+						}
+					}
+					Application.Driver.AddRune (rune);
+					Driver.SetAttribute (GetNormalColor ());
+				}
+			} else {
+				DrawHotString (rl, HasFocus && i == _cursor, ColorScheme);
 			}
 		}
+	}
 
-		/// <summary>
-		/// Allow to invoke the <see cref="SelectedItemChanged"/> after their creation.
-		/// </summary>
-		public void Refresh ()
-		{
-			OnSelectedItemChanged (selected, -1);
+	///<inheritdoc/>
+	public override void PositionCursor ()
+	{
+		switch (DisplayMode) {
+		case DisplayModeLayout.Vertical:
+			Move (0, _cursor);
+			break;
+		case DisplayModeLayout.Horizontal:
+			Move (_horizontal [_cursor].pos, 0);
+			break;
 		}
+	}
 
-		/// <summary>
-		/// Called whenever the current selected item changes. Invokes the <see cref="SelectedItemChanged"/> event.
-		/// </summary>
-		/// <param name="selectedItem"></param>
-		/// <param name="previousSelectedItem"></param>
-		public virtual void OnSelectedItemChanged (int selectedItem, int previousSelectedItem)
-		{
-			selected = selectedItem;
-			SelectedItemChanged?.Invoke (this, new SelectedItemChangedArgs (selectedItem, previousSelectedItem));
-		}
+	/// <summary>
+	/// Invoked when the selected radio label has changed.
+	/// </summary>
+	public event EventHandler<SelectedItemChangedArgs> SelectedItemChanged;
 
-		///<inheritdoc/>
-		public override bool ProcessColdKey (KeyEvent kb)
-		{
-			var key = kb.KeyValue;
-			if (key < Char.MaxValue && Char.IsLetterOrDigit ((char)key)) {
-				int i = 0;
-				key = Char.ToUpper ((char)key);
-				foreach (var l in radioLabels) {
-					bool nextIsHot = false;
-					TextFormatter.FindHotKey (l, HotKeySpecifier, true, out _, out Key hotKey);
-					foreach (Rune c in l) {
-						if (c == HotKeySpecifier) {
-							nextIsHot = true;
-						} else {
-							if ((nextIsHot && Rune.ToUpperInvariant (c).Value == key) || (key == (uint)hotKey)) {
-								SelectedItem = i;
-								cursor = i;
-								if (!HasFocus)
-									SetFocus ();
-								return true;
-							}
-							nextIsHot = false;
-						}
-					}
-					i++;
-				}
-			}
-			return false;
+	/// <summary>
+	/// The currently selected item from the list of radio labels
+	/// </summary>
+	/// <value>The selected.</value>
+	public int SelectedItem {
+		get => _selected;
+		set {
+			OnSelectedItemChanged (value, SelectedItem);
+			_cursor = _selected;
+			SetNeedsDisplay ();
 		}
+	}
 
-		///<inheritdoc/>
-		public override bool ProcessKey (KeyEvent kb)
-		{
-			var result = InvokeKeybindings (kb);
-			if (result != null)
-				return (bool)result;
+	/// <summary>
+	/// Allow to invoke the <see cref="SelectedItemChanged"/> after their creation.
+	/// </summary>
+	public void Refresh ()
+	{
+		OnSelectedItemChanged (_selected, -1);
+	}
 
-			return base.ProcessKey (kb);
-		}
+	/// <summary>
+	/// Called whenever the current selected item changes. Invokes the <see cref="SelectedItemChanged"/> event.
+	/// </summary>
+	/// <param name="selectedItem"></param>
+	/// <param name="previousSelectedItem"></param>
+	public virtual void OnSelectedItemChanged (int selectedItem, int previousSelectedItem)
+	{
+		_selected = selectedItem;
+		SelectedItemChanged?.Invoke (this, new SelectedItemChangedArgs (selectedItem, previousSelectedItem));
+	}
 
-		void SelectItem ()
-		{
-			SelectedItem = cursor;
-		}
+	void SelectItem ()
+	{
+		SelectedItem = _cursor;
+	}
 
-		void MoveEnd ()
-		{
-			cursor = Math.Max (radioLabels.Count - 1, 0);
-		}
+	void MoveEnd ()
+	{
+		_cursor = Math.Max (_radioLabels.Count - 1, 0);
+	}
 
-		void MoveHome ()
-		{
-			cursor = 0;
-		}
+	void MoveHome ()
+	{
+		_cursor = 0;
+	}
 
-		void MoveDown ()
-		{
-			if (cursor + 1 < radioLabels.Count) {
-				cursor++;
-				SetNeedsDisplay ();
-			} else if (cursor > 0) {
-				cursor = 0;
-				SetNeedsDisplay ();
-			}
+	void MoveDown ()
+	{
+		if (_cursor + 1 < _radioLabels.Count) {
+			_cursor++;
+			SetNeedsDisplay ();
+		} else if (_cursor > 0) {
+			_cursor = 0;
+			SetNeedsDisplay ();
 		}
+	}
 
-		void MoveUp ()
-		{
-			if (cursor > 0) {
-				cursor--;
-				SetNeedsDisplay ();
-			} else if (radioLabels.Count - 1 > 0) {
-				cursor = radioLabels.Count - 1;
-				SetNeedsDisplay ();
-			}
+	void MoveUp ()
+	{
+		if (_cursor > 0) {
+			_cursor--;
+			SetNeedsDisplay ();
+		} else if (_radioLabels.Count - 1 > 0) {
+			_cursor = _radioLabels.Count - 1;
+			SetNeedsDisplay ();
 		}
+	}
 
-		///<inheritdoc/>
-		public override bool MouseEvent (MouseEvent me)
-		{
-			if (!me.Flags.HasFlag (MouseFlags.Button1Clicked)) {
-				return false;
-			}
-			if (!CanFocus) {
-				return false;
-			}
-			SetFocus ();
+	///<inheritdoc/>
+	public override bool MouseEvent (MouseEvent me)
+	{
+		if (!me.Flags.HasFlag (MouseFlags.Button1Clicked)) {
+			return false;
+		}
+		if (!CanFocus) {
+			return false;
+		}
+		SetFocus ();
 
-			int boundsX = me.X;
-			int boundsY = me.Y;
+		int boundsX = me.X;
+		int boundsY = me.Y;
 
-			var pos = displayMode == DisplayModeLayout.Horizontal ? boundsX : boundsY;
-			var rCount = displayMode == DisplayModeLayout.Horizontal ? horizontal.Last ().pos + horizontal.Last ().length : radioLabels.Count;
+		var pos = _displayMode == DisplayModeLayout.Horizontal ? boundsX : boundsY;
+		var rCount = _displayMode == DisplayModeLayout.Horizontal ? _horizontal.Last ().pos + _horizontal.Last ().length : _radioLabels.Count;
 
-			if (pos < rCount) {
-				var c = displayMode == DisplayModeLayout.Horizontal ? horizontal.FindIndex ((x) => x.pos <= boundsX && x.pos + x.length - 2 >= boundsX) : boundsY;
-				if (c > -1) {
-					cursor = SelectedItem = c;
-					SetNeedsDisplay ();
-				}
+		if (pos < rCount) {
+			var c = _displayMode == DisplayModeLayout.Horizontal ? _horizontal.FindIndex ((x) => x.pos <= boundsX && x.pos + x.length - 2 >= boundsX) : boundsY;
+			if (c > -1) {
+				_cursor = SelectedItem = c;
+				SetNeedsDisplay ();
 			}
-			return true;
 		}
+		return true;
+	}
 
-		///<inheritdoc/>
-		public override bool OnEnter (View view)
-		{
-			Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
+	///<inheritdoc/>
+	public override bool OnEnter (View view)
+	{
+		Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
 
-			return base.OnEnter (view);
-		}
+		return base.OnEnter (view);
 	}
+}
 
+/// <summary>
+/// Used for choose the display mode of this <see cref="RadioGroup"/>
+/// </summary>
+public enum DisplayModeLayout {
 	/// <summary>
-	/// Used for choose the display mode of this <see cref="RadioGroup"/>
+	/// Vertical mode display. It's the default.
 	/// </summary>
-	public enum DisplayModeLayout {
-		/// <summary>
-		/// Vertical mode display. It's the default.
-		/// </summary>
-		Vertical,
-		/// <summary>
-		/// Horizontal mode display.
-		/// </summary>
-		Horizontal
-	}
+	Vertical,
+	/// <summary>
+	/// Horizontal mode display.
+	/// </summary>
+	Horizontal
 }

+ 17 - 17
Terminal.Gui/Views/ScrollView.cs

@@ -104,23 +104,23 @@ public class ScrollView : View {
 		AddCommand (Command.RightEnd, () => ScrollRight (_contentSize.Width));
 
 		// Default keybindings for this view
-		AddKeyBinding (Key.CursorUp, Command.ScrollUp);
-		AddKeyBinding (Key.CursorDown, Command.ScrollDown);
-		AddKeyBinding (Key.CursorLeft, Command.ScrollLeft);
-		AddKeyBinding (Key.CursorRight, Command.ScrollRight);
+		KeyBindings.Add (KeyCode.CursorUp, Command.ScrollUp);
+		KeyBindings.Add (KeyCode.CursorDown, Command.ScrollDown);
+		KeyBindings.Add (KeyCode.CursorLeft, Command.ScrollLeft);
+		KeyBindings.Add (KeyCode.CursorRight, Command.ScrollRight);
 
-		AddKeyBinding (Key.PageUp, Command.PageUp);
-		AddKeyBinding ((Key)'v' | Key.AltMask, Command.PageUp);
+		KeyBindings.Add (KeyCode.PageUp, Command.PageUp);
+		KeyBindings.Add ((KeyCode)'v' | KeyCode.AltMask, Command.PageUp);
 
-		AddKeyBinding (Key.PageDown, Command.PageDown);
-		AddKeyBinding (Key.V | Key.CtrlMask, Command.PageDown);
+		KeyBindings.Add (KeyCode.PageDown, Command.PageDown);
+		KeyBindings.Add (KeyCode.V | KeyCode.CtrlMask, Command.PageDown);
 
-		AddKeyBinding (Key.PageUp | Key.CtrlMask, Command.PageLeft);
-		AddKeyBinding (Key.PageDown | Key.CtrlMask, Command.PageRight);
-		AddKeyBinding (Key.Home, Command.TopHome);
-		AddKeyBinding (Key.End, Command.BottomEnd);
-		AddKeyBinding (Key.Home | Key.CtrlMask, Command.LeftHome);
-		AddKeyBinding (Key.End | Key.CtrlMask, Command.RightEnd);
+		KeyBindings.Add (KeyCode.PageUp | KeyCode.CtrlMask, Command.PageLeft);
+		KeyBindings.Add (KeyCode.PageDown | KeyCode.CtrlMask, Command.PageRight);
+		KeyBindings.Add (KeyCode.Home, Command.TopHome);
+		KeyBindings.Add (KeyCode.End, Command.BottomEnd);
+		KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask, Command.LeftHome);
+		KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask, Command.RightEnd);
 
 		Initialized += (s, e) => {
 			if (!_vertical.IsInitialized) {
@@ -563,12 +563,12 @@ public class ScrollView : View {
 	}
 
 	///<inheritdoc/>
-	public override bool ProcessKey (KeyEvent kb)
+	public override bool OnKeyDown (Key a)
 	{
-		if (base.ProcessKey (kb))
+		if (base.OnKeyDown (a))
 			return true;
 
-		var result = InvokeKeybindings (kb);
+		var result = InvokeKeyBindings (a);
 		if (result != null)
 			return (bool)result;
 

+ 21 - 35
Terminal.Gui/Views/Slider.cs

@@ -1403,48 +1403,34 @@ public class Slider<T> : View {
 	void SetKeyBindings ()
 	{
 		if (_config._sliderOrientation == Orientation.Horizontal) {
-			AddKeyBinding (Key.CursorRight, Command.Right);
-			ClearKeyBinding (Key.CursorDown);
-			AddKeyBinding (Key.CursorLeft, Command.Left);
-			ClearKeyBinding (Key.CursorUp);
-
-			AddKeyBinding (Key.CursorRight | Key.CtrlMask, Command.RightExtend);
-			ClearKeyBinding (Key.CursorDown | Key.CtrlMask);
-			AddKeyBinding (Key.CursorLeft | Key.CtrlMask, Command.LeftExtend);
-			ClearKeyBinding (Key.CursorUp | Key.CtrlMask);
+			KeyBindings.Add (KeyCode.CursorRight, Command.Right);
+			KeyBindings.Remove (KeyCode.CursorDown);
+			KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
+			KeyBindings.Remove (KeyCode.CursorUp);
+
+			KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask, Command.RightExtend);
+			KeyBindings.Remove (KeyCode.CursorDown | KeyCode.CtrlMask);
+			KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask, Command.LeftExtend);
+			KeyBindings.Remove (KeyCode.CursorUp | KeyCode.CtrlMask);
 		} else {
-			ClearKeyBinding (Key.CursorRight);
-			AddKeyBinding (Key.CursorDown, Command.LineDown);
-			ClearKeyBinding (Key.CursorLeft);
-			AddKeyBinding (Key.CursorUp, Command.LineUp);
+			KeyBindings.Remove (KeyCode.CursorRight);
+			KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
+			KeyBindings.Remove (KeyCode.CursorLeft);
+			KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
 
-			ClearKeyBinding (Key.CursorRight | Key.CtrlMask);
-			AddKeyBinding (Key.CursorDown | Key.CtrlMask, Command.RightExtend);
-			ClearKeyBinding (Key.CursorLeft | Key.CtrlMask);
-			AddKeyBinding (Key.CursorUp | Key.CtrlMask, Command.LeftExtend);
+			KeyBindings.Remove (KeyCode.CursorRight | KeyCode.CtrlMask);
+			KeyBindings.Add (KeyCode.CursorDown | KeyCode.CtrlMask, Command.RightExtend);
+			KeyBindings.Remove (KeyCode.CursorLeft | KeyCode.CtrlMask);
+			KeyBindings.Add (KeyCode.CursorUp | KeyCode.CtrlMask, Command.LeftExtend);
 
 		}
-		AddKeyBinding (Key.Home, Command.LeftHome);
-		AddKeyBinding (Key.End, Command.RightEnd);
-		AddKeyBinding (Key.Enter, Command.Accept);
-		AddKeyBinding (Key.Space, Command.Accept);
+		KeyBindings.Add (KeyCode.Home, Command.LeftHome);
+		KeyBindings.Add (KeyCode.End, Command.RightEnd);
+		KeyBindings.Add (KeyCode.Enter, Command.Accept);
+		KeyBindings.Add (KeyCode.Space, Command.Accept);
 
 	}
 
-	/// <inheritdoc/>
-	public override bool ProcessKey (KeyEvent keyEvent)
-	{
-		if (!CanFocus || !HasFocus) {
-			return base.ProcessKey (keyEvent);
-		}
-
-		var result = InvokeKeybindings (keyEvent);
-		if (result != null) {
-			return (bool)result;
-		}
-		return base.ProcessKey (keyEvent);
-	}
-
 	Dictionary<int, SliderOption<T>> GetSetOptionDictionary () => _setOptions.ToDictionary (e => e, e => _options [e]);
 
 	void SetFocusedOption ()

+ 245 - 222
Terminal.Gui/Views/StatusBar.cs

@@ -1,273 +1,296 @@
-//
-// StatusBar.cs: a statusbar for an application
-//
-// Authors:
-//   Miguel de Icaza ([email protected])
-//
-// TODO:
-//   Add mouse support
 using System;
 using System.Collections.Generic;
 using System.Text;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui;
+/// <summary>
+/// <see cref="StatusItem"/> objects are contained by <see cref="StatusBar"/> <see cref="View"/>s. 
+/// Each <see cref="StatusItem"/> has a title, a shortcut (hotkey), and an <see cref="Command"/> that will be invoked when the 
+/// <see cref="StatusItem.Shortcut"/> is pressed.
+/// The <see cref="StatusItem.Shortcut"/> will be a global hotkey for the application in the current context of the screen.
+/// The color of the <see cref="StatusItem.Title"/> will be changed after each ~. 
+/// A <see cref="StatusItem.Title"/> set to `~F1~ Help` will render as *F1* using <see cref="ColorScheme.HotNormal"/> and
+/// *Help* as <see cref="ColorScheme.HotNormal"/>.
+/// </summary>
+public class StatusItem {
 	/// <summary>
-	/// <see cref="StatusItem"/> objects are contained by <see cref="StatusBar"/> <see cref="View"/>s. 
-	/// Each <see cref="StatusItem"/> has a title, a shortcut (hotkey), and an <see cref="Action"/> that will be invoked when the 
-	/// <see cref="StatusItem.Shortcut"/> is pressed.
-	/// The <see cref="StatusItem.Shortcut"/> will be a global hotkey for the application in the current context of the screen.
+	/// Initializes a new <see cref="StatusItem"/>.
+	/// </summary>
+	/// <param name="shortcut">Shortcut to activate the <see cref="StatusItem"/>.</param>
+	/// <param name="title">Title for the <see cref="StatusItem"/>.</param>
+	/// <param name="action">Action to invoke when the <see cref="StatusItem"/> is activated.</param>
+	/// <param name="canExecute">Function to determine if the action can currently be executed.</param>
+	public StatusItem (Key shortcut, string title, Action action, Func<bool> canExecute = null)
+	{
+		Title = title ?? "";
+		Shortcut = shortcut;
+		Action = action;
+		CanExecute = canExecute;
+	}
+
+	/// <summary>
+	/// Gets the global shortcut to invoke the action on the menu.
+	/// </summary>
+	public Key Shortcut { get; set; }
+
+	/// <summary>
+	/// Gets or sets the title.
+	/// </summary>
+	/// <value>The title.</value>
+	/// <remarks>
 	/// The colour of the <see cref="StatusItem.Title"/> will be changed after each ~. 
 	/// A <see cref="StatusItem.Title"/> set to `~F1~ Help` will render as *F1* using <see cref="ColorScheme.HotNormal"/> and
 	/// *Help* as <see cref="ColorScheme.HotNormal"/>.
-	/// </summary>
-	public class StatusItem {
-		/// <summary>
-		/// Initializes a new <see cref="StatusItem"/>.
-		/// </summary>
-		/// <param name="shortcut">Shortcut to activate the <see cref="StatusItem"/>.</param>
-		/// <param name="title">Title for the <see cref="StatusItem"/>.</param>
-		/// <param name="action">Action to invoke when the <see cref="StatusItem"/> is activated.</param>
-		/// <param name="canExecute">Function to determine if the action can currently be executed.</param>
-		public StatusItem (Key shortcut, string title, Action action, Func<bool> canExecute = null)
-		{
-			Title = title ?? "";
-			Shortcut = shortcut;
-			Action = action;
-			CanExecute = canExecute;
-		}
+	/// </remarks>
+	public string Title { get; set; }
 
-		/// <summary>
-		/// Gets the global shortcut to invoke the action on the menu.
-		/// </summary>
-		public Key Shortcut { get; set; }
+	/// <summary>
+	/// Gets or sets the action to be invoked when the statusbar item is triggered
+	/// </summary>
+	/// <value>Action to invoke.</value>
+	public Action Action { get; set; }
 
-		/// <summary>
-		/// Gets or sets the title.
-		/// </summary>
-		/// <value>The title.</value>
-		/// <remarks>
-		/// The colour of the <see cref="StatusItem.Title"/> will be changed after each ~. 
-		/// A <see cref="StatusItem.Title"/> set to `~F1~ Help` will render as *F1* using <see cref="ColorScheme.HotNormal"/> and
-		/// *Help* as <see cref="ColorScheme.HotNormal"/>.
-		/// </remarks>
-		public string Title { get; set; }
+	/// <summary>
+	/// Gets or sets the action to be invoked to determine if the <see cref="StatusItem"/> can be triggered. 
+	/// If <see cref="CanExecute"/> returns <see langword="true"/> the status item will be enabled. Otherwise, it will be disabled.
+	/// </summary>
+	/// <value>Function to determine if the action is can be executed or not.</value>
+	public Func<bool> CanExecute { get; set; }
 
-		/// <summary>
-		/// Gets or sets the action to be invoked when the statusbar item is triggered
-		/// </summary>
-		/// <value>Action to invoke.</value>
-		public Action Action { get; set; }
+	/// <summary>
+	/// Returns <see langword="true"/> if the status item is enabled. This method is a wrapper around <see cref="CanExecute"/>.
+	/// </summary>
+	public bool IsEnabled ()
+	{
+		return CanExecute?.Invoke () ?? true;
+	}
 
-		/// <summary>
-		/// Gets or sets the action to be invoked to determine if the <see cref="StatusItem"/> can be triggered. 
-		/// If <see cref="CanExecute"/> returns <see langword="true"/> the status item will be enabled. Otherwise, it will be disabled.
-		/// </summary>
-		/// <value>Function to determine if the action is can be executed or not.</value>
-		public Func<bool> CanExecute { get; set; }
+	/// <summary>
+	/// Gets or sets arbitrary data for the status item.
+	/// </summary>
+	/// <remarks>This property is not used internally.</remarks>
+	public object Data { get; set; }
+};
 
-		/// <summary>
-		/// Returns <see langword="true"/> if the status item is enabled. This method is a wrapper around <see cref="CanExecute"/>.
-		/// </summary>
-		public bool IsEnabled ()
-		{
-			return CanExecute == null ? true : CanExecute ();
+/// <summary>
+/// A status bar is a <see cref="View"/> that snaps to the bottom of a <see cref="Toplevel"/> displaying set of <see cref="StatusItem"/>s.
+/// The <see cref="StatusBar"/> should be context sensitive. This means, if the main menu and an open text editor are visible, the items probably shown will
+/// be ~F1~ Help ~F2~ Save ~F3~ Load. While a dialog to ask a file to load is executed, the remaining commands will probably be ~F1~ Help.
+/// So for each context must be a new instance of a status bar.
+/// </summary>
+public class StatusBar : View {
+	/// <summary>
+	/// The items that compose the <see cref="StatusBar"/>
+	/// </summary>
+	public StatusItem [] Items {
+		get => _items;
+		set {
+			foreach (var item in _items) {
+				KeyBindings.Remove ((KeyCode)item.Shortcut);
+			}
+			_items = value;
+			foreach (var item in _items) {
+				KeyBindings.Add ((KeyCode)item.Shortcut, KeyBindingScope.HotKey, Command.Accept);
+			}
 		}
-
-		/// <summary>
-		/// Gets or sets arbitrary data for the status item.
-		/// </summary>
-		/// <remarks>This property is not used internally.</remarks>
-		public object Data { get; set; }
-	};
+	}
 
 	/// <summary>
-	/// A status bar is a <see cref="View"/> that snaps to the bottom of a <see cref="Toplevel"/> displaying set of <see cref="StatusItem"/>s.
-	/// The <see cref="StatusBar"/> should be context sensitive. This means, if the main menu and an open text editor are visible, the items probably shown will
-	/// be ~F1~ Help ~F2~ Save ~F3~ Load. While a dialog to ask a file to load is executed, the remaining commands will probably be ~F1~ Help.
-	/// So for each context must be a new instance of a statusbar.
+	/// Initializes a new instance of the <see cref="StatusBar"/> class.
 	/// </summary>
-	public class StatusBar : View {
-		/// <summary>
-		/// The items that compose the <see cref="StatusBar"/>
-		/// </summary>
-		public StatusItem [] Items { get; set; }
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="StatusBar"/> class.
-		/// </summary>
-		public StatusBar () : this (items: new StatusItem [] { }) { }
+	public StatusBar () : this (items: new StatusItem [] { }) { }
 
-		/// <summary>
-		/// Initializes a new instance of the <see cref="StatusBar"/> class with the specified set of <see cref="StatusItem"/>s.
-		/// The <see cref="StatusBar"/> will be drawn on the lowest line of the terminal or <see cref="View.SuperView"/> (if not null).
-		/// </summary>
-		/// <param name="items">A list of statusbar items.</param>
-		public StatusBar (StatusItem [] items) : base ()
-		{
+	/// <summary>
+	/// Initializes a new instance of the <see cref="StatusBar"/> class with the specified set of <see cref="StatusItem"/>s.
+	/// The <see cref="StatusBar"/> will be drawn on the lowest line of the terminal or <see cref="View.SuperView"/> (if not null).
+	/// </summary>
+	/// <param name="items">A list of status bar items.</param>
+	public StatusBar (StatusItem [] items) : base ()
+	{
+		if (items != null) {
 			Items = items;
-			CanFocus = false;
-			ColorScheme = Colors.Menu;
-			X = 0;
-			Y = Pos.AnchorEnd (1);
-			Width = Dim.Fill ();
-			Height = 1;
 		}
+		CanFocus = false;
+		ColorScheme = Colors.Menu;
+		X = 0;
+		Y = Pos.AnchorEnd (1);
+		Width = Dim.Fill ();
+		Height = 1;
+		AddCommand (Command.Accept, InvokeItem);
+	}
+
+	StatusItem _itemToInvoke;
+	bool? InvokeItem ()
+	{
+		if (_itemToInvoke is { Action: not null }) {
+			_itemToInvoke.Action.Invoke ();
+			return true;
+		}
+		return false;
+	}
 
-		static string shortcutDelimiter = "-";
-		/// <summary>
-		/// Used for change the shortcut delimiter separator.
-		/// </summary>
-		public static string ShortcutDelimiter {
-			get => shortcutDelimiter;
-			set {
-				if (shortcutDelimiter != value) {
-					shortcutDelimiter = value == string.Empty ? " " : value;
+	/// <inheritdoc/>
+	public override bool? OnInvokingKeyBindings (Key keyEvent)
+	{
+		// This is a bit of a hack. We want to handle the key bindings for status bar but
+		// InvokeKeyBindings doesn't pass any context so we can't tell which item it is for.
+		// So before we call the base class we set SelectedItem appropriately.
+		var key = keyEvent.KeyCode;
+		if (KeyBindings.TryGet(key, out _)) {
+			// Search RadioLabels 
+			foreach (var item in Items) {
+				if (item.Shortcut == key) {
+					_itemToInvoke = item;
+					keyEvent.Scope = KeyBindingScope.HotKey;
+					break;
 				}
 			}
-		}
 
-		Attribute ToggleScheme (Attribute scheme)
-		{
-			var result = scheme == ColorScheme.Normal ? ColorScheme.HotNormal : ColorScheme.Normal;
-			Driver.SetAttribute (result);
-			return result;
 		}
+		return base.OnInvokingKeyBindings (keyEvent);
+	}
+	static Rune _shortcutDelimiter = (Rune)'=';
+	StatusItem [] _items = new StatusItem [] { };
 
-		Attribute DetermineColorSchemeFor (StatusItem item)
-		{
-			if (item != null) {
-				if (item.IsEnabled ()) {
-					return GetNormalColor ();
-				}
-				return ColorScheme.Disabled;
+	/// <summary>
+	/// Gets or sets shortcut delimiter separator. The default is "-".
+	/// </summary>
+	public static Rune ShortcutDelimiter {
+		get => _shortcutDelimiter;
+		set {
+			if (_shortcutDelimiter != value) {
+				_shortcutDelimiter = value == default ? (Rune)'=' : value;
 			}
-			return GetNormalColor ();
 		}
+	}
 
-		///<inheritdoc/>
-		public override void OnDrawContent (Rect contentArea)
-		{
-			Move (0, 0);
-			Driver.SetAttribute (GetNormalColor ());
-			for (int i = 0; i < Frame.Width; i++) {
-				Driver.AddRune ((Rune)' ');
-			}
+	Attribute ToggleScheme (Attribute scheme)
+	{
+		var result = scheme == ColorScheme.Normal ? ColorScheme.HotNormal : ColorScheme.Normal;
+		Driver.SetAttribute (result);
+		return result;
+	}
 
-			Move (1, 0);
-			var scheme = GetNormalColor ();
-			Driver.SetAttribute (scheme);
-			for (int i = 0; i < Items.Length; i++) {
-				var title = Items [i].Title;
-				Driver.SetAttribute (DetermineColorSchemeFor (Items [i]));
-				for (int n = 0; n < Items [i].Title.GetRuneCount (); n++) {
-					if (title [n] == '~') {
-						if (Items [i].IsEnabled ()) {
-							scheme = ToggleScheme (scheme);
-						}
-						continue;
-					}
-					Driver.AddRune ((Rune)title [n]);
-				}
-				if (i + 1 < Items.Length) {
-					Driver.AddRune ((Rune)' ');
-					Driver.AddRune (CM.Glyphs.VLine);
-					Driver.AddRune ((Rune)' ');
-				}
+	Attribute DetermineColorSchemeFor (StatusItem item)
+	{
+		if (item != null) {
+			if (item.IsEnabled ()) {
+				return GetNormalColor ();
 			}
+			return ColorScheme.Disabled;
 		}
+		return GetNormalColor ();
+	}
 
-		///<inheritdoc/>
-		public override bool ProcessHotKey (KeyEvent kb)
-		{
-			foreach (var item in Items) {
-				if (kb.Key == item.Shortcut) {
-					if (item.IsEnabled ()) {
-						Run (item.Action);
-					}
-					return true;
-				}
-			}
-			return false;
+	///<inheritdoc/>
+	public override void OnDrawContent (Rect contentArea)
+	{
+		Move (0, 0);
+		Driver.SetAttribute (GetNormalColor ());
+		for (int i = 0; i < Frame.Width; i++) {
+			Driver.AddRune ((Rune)' ');
 		}
 
-		///<inheritdoc/>
-		public override bool MouseEvent (MouseEvent me)
-		{
-			if (me.Flags != MouseFlags.Button1Clicked)
-				return false;
-
-			int pos = 1;
-			for (int i = 0; i < Items.Length; i++) {
-				if (me.X >= pos && me.X < pos + GetItemTitleLength (Items [i].Title)) {
-					var item = Items [i];
-					if (item.IsEnabled ()) {
-						Run (item.Action);
+		Move (1, 0);
+		var scheme = GetNormalColor ();
+		Driver.SetAttribute (scheme);
+		for (int i = 0; i < Items.Length; i++) {
+			var title = Items [i].Title;
+			Driver.SetAttribute (DetermineColorSchemeFor (Items [i]));
+			for (int n = 0; n < Items [i].Title.GetRuneCount (); n++) {
+				if (title [n] == '~') {
+					if (Items [i].IsEnabled ()) {
+						scheme = ToggleScheme (scheme);
 					}
-					break;
+					continue;
 				}
-				pos += GetItemTitleLength (Items [i].Title) + 3;
+				Driver.AddRune ((Rune)title [n]);
+			}
+			if (i + 1 < Items.Length) {
+				Driver.AddRune ((Rune)' ');
+				Driver.AddRune (CM.Glyphs.VLine);
+				Driver.AddRune ((Rune)' ');
 			}
-			return true;
 		}
+	}
+	
+	///<inheritdoc/>
+	public override bool MouseEvent (MouseEvent me)
+	{
+		if (me.Flags != MouseFlags.Button1Clicked)
+			return false;
 
-		int GetItemTitleLength (string title)
-		{
-			int len = 0;
-			foreach (var ch in title) {
-				if (ch == '~')
-					continue;
-				len++;
+		int pos = 1;
+		for (int i = 0; i < Items.Length; i++) {
+			if (me.X >= pos && me.X < pos + GetItemTitleLength (Items [i].Title)) {
+				var item = Items [i];
+				if (item.IsEnabled ()) {
+					Run (item.Action);
+				}
+				break;
 			}
+			pos += GetItemTitleLength (Items [i].Title) + 3;
+		}
+		return true;
+	}
 
-			return len;
+	int GetItemTitleLength (string title)
+	{
+		int len = 0;
+		foreach (var ch in title) {
+			if (ch == '~')
+				continue;
+			len++;
 		}
 
-		void Run (Action action)
-		{
-			if (action == null)
-				return;
+		return len;
+	}
 
-			Application.MainLoop.AddIdle (() => {
-				action ();
-				return false;
-			});
-		}
+	void Run (Action action)
+	{
+		if (action == null)
+			return;
+
+		Application.MainLoop.AddIdle (() => {
+			action ();
+			return false;
+		});
+	}
 
-		///<inheritdoc/>
-		public override bool OnEnter (View view)
-		{
-			Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
+	///<inheritdoc/>
+	public override bool OnEnter (View view)
+	{
+		Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
 
-			return base.OnEnter (view);
-		}
+		return base.OnEnter (view);
+	}
 
-		/// <summary>
-		/// Inserts a <see cref="StatusItem"/> in the specified index of <see cref="Items"/>.
-		/// </summary>
-		/// <param name="index">The zero-based index at which item should be inserted.</param>
-		/// <param name="item">The item to insert.</param>
-		public void AddItemAt (int index, StatusItem item)
-		{
-			var itemsList = new List<StatusItem> (Items);
-			itemsList.Insert (index, item);
-			Items = itemsList.ToArray ();
-			SetNeedsDisplay ();
-		}
+	/// <summary>
+	/// Inserts a <see cref="StatusItem"/> in the specified index of <see cref="Items"/>.
+	/// </summary>
+	/// <param name="index">The zero-based index at which item should be inserted.</param>
+	/// <param name="item">The item to insert.</param>
+	public void AddItemAt (int index, StatusItem item)
+	{
+		var itemsList = new List<StatusItem> (Items);
+		itemsList.Insert (index, item);
+		Items = itemsList.ToArray ();
+		SetNeedsDisplay ();
+	}
 
-		/// <summary>
-		/// Removes a <see cref="StatusItem"/> at specified index of <see cref="Items"/>.
-		/// </summary>
-		/// <param name="index">The zero-based index of the item to remove.</param>
-		/// <returns>The <see cref="StatusItem"/> removed.</returns>
-		public StatusItem RemoveItem (int index)
-		{
-			var itemsList = new List<StatusItem> (Items);
-			var item = itemsList [index];
-			itemsList.RemoveAt (index);
-			Items = itemsList.ToArray ();
-			SetNeedsDisplay ();
+	/// <summary>
+	/// Removes a <see cref="StatusItem"/> at specified index of <see cref="Items"/>.
+	/// </summary>
+	/// <param name="index">The zero-based index of the item to remove.</param>
+	/// <returns>The <see cref="StatusItem"/> removed.</returns>
+	public StatusItem RemoveItem (int index)
+	{
+		var itemsList = new List<StatusItem> (Items);
+		var item = itemsList [index];
+		itemsList.RemoveAt (index);
+		Items = itemsList.ToArray ();
+		SetNeedsDisplay ();
 
-			return item;
-		}
+		return item;
 	}
-}
+}

+ 4 - 16
Terminal.Gui/Views/TabView.cs

@@ -123,10 +123,10 @@ namespace Terminal.Gui {
 			AddCommand (Command.RightEnd, () => { SelectedTab = Tabs.LastOrDefault (); return true; });
 
 			// Default keybindings for this view
-			AddKeyBinding (Key.CursorLeft, Command.Left);
-			AddKeyBinding (Key.CursorRight, Command.Right);
-			AddKeyBinding (Key.Home, Command.LeftHome);
-			AddKeyBinding (Key.End, Command.RightEnd);
+			KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
+			KeyBindings.Add (KeyCode.CursorRight, Command.Right);
+			KeyBindings.Add (KeyCode.Home, Command.LeftHome);
+			KeyBindings.Add (KeyCode.End, Command.RightEnd);
 		}
 
 		/// <summary>
@@ -229,18 +229,6 @@ namespace Terminal.Gui {
 			SelectedTabChanged?.Invoke (this, new TabChangedEventArgs (oldTab, newTab));
 		}
 
-		/// <inheritdoc/>
-		public override bool ProcessKey (KeyEvent keyEvent)
-		{
-			if (HasFocus && CanFocus && Focused == tabsBar) {
-				var result = InvokeKeybindings (keyEvent);
-				if (result != null)
-					return (bool)result;
-			}
-
-			return base.ProcessKey (keyEvent);
-		}
-
 		/// <summary>
 		/// Changes the <see cref="SelectedTab"/> by the given <paramref name="amount"/>.  
 		/// Positive for right, negative for left.  If no tab is currently selected then

+ 1 - 1
Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapper.cs

@@ -29,7 +29,7 @@ namespace Terminal.Gui {
 			this.Wrapping = toWrap;
 			this.tableView = tableView;
 
-			tableView.AddKeyBinding (Key.Space, Command.ToggleChecked);
+			tableView.KeyBindings.Add (KeyCode.Space, Command.ToggleChecked);
 
 			tableView.MouseClick += TableView_MouseClick;
 			tableView.CellToggled += TableView_CellToggled;

+ 60 - 61
Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapperByObject.cs

@@ -1,76 +1,75 @@
 using System;
 using System.Linq;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui;
+/// <summary>
+/// Implementation of <see cref="CheckBoxTableSourceWrapperBase"/> which records toggled rows
+/// by a property on row objects.
+/// </summary>
+public class CheckBoxTableSourceWrapperByObject<T> : CheckBoxTableSourceWrapperBase {
+	private readonly IEnumerableTableSource<T> _toWrap;
+	readonly Func<T, bool> _getter;
+	readonly Action<T, bool> _setter;
+
 	/// <summary>
-	/// Implementation of <see cref="CheckBoxTableSourceWrapperBase"/> which records toggled rows
-	/// by a property on row objects.
+	/// Creates a new instance of the class wrapping the collection <paramref name="toWrap"/>.
 	/// </summary>
-	public class CheckBoxTableSourceWrapperByObject<T> : CheckBoxTableSourceWrapperBase {
-		private readonly IEnumerableTableSource<T> toWrap;
-		readonly Func<T, bool> getter;
-		readonly Action<T, bool> setter;
+	/// <param name="tableView">The table you will use the source with.</param>
+	/// <param name="toWrap">The collection of objects you will record checked state for</param>
+	/// <param name="getter">Delegate method for retrieving checked state from your objects of type <typeparamref name="T"/>.</param>
+	/// <param name="setter">Delegate method for setting new checked states on your objects of type <typeparamref name="T"/>.</param>
+	public CheckBoxTableSourceWrapperByObject (
+		TableView tableView,
+		IEnumerableTableSource<T> toWrap,
+		Func<T, bool> getter,
+		Action<T, bool> setter) : base (tableView, toWrap)
+	{
+		this._toWrap = toWrap;
+		this._getter = getter;
+		this._setter = setter;
+	}
 
-		/// <summary>
-		/// Creates a new instance of the class wrapping the collection <see cref="toWrap"/>.
-		/// </summary>
-		/// <param name="tableView">The table you will use the source with.</param>
-		/// <param name="toWrap">The collection of objects you will record checked state for</param>
-		/// <param name="getter">Delegate method for retrieving checked state from your objects of type <typeparamref name="T"/>.</param>
-		/// <param name="setter">Delegate method for setting new checked states on your objects of type <typeparamref name="T"/>.</param>
-		public CheckBoxTableSourceWrapperByObject (
-			TableView tableView,
-			IEnumerableTableSource<T> toWrap,
-			Func<T,bool> getter,
-			Action<T,bool> setter) : base (tableView, toWrap)
-		{
-			this.toWrap = toWrap;
-			this.getter = getter;
-			this.setter = setter;
-		}
+	/// <inheritdoc/>
+	protected override bool IsChecked (int row)
+	{
+		return _getter (_toWrap.GetObjectOnRow (row));
+	}
 
-		/// <inheritdoc/>
-		protected override bool IsChecked (int row)
-		{
-			return getter (toWrap.GetObjectOnRow (row));
-		}
+	/// <inheritdoc/>
+	protected override void ToggleAllRows ()
+	{
+		ToggleRows (Enumerable.Range (0, _toWrap.Rows).ToArray ());
+	}
 
-		/// <inheritdoc/>
-		protected override void ToggleAllRows ()
-		{
-			ToggleRows (Enumerable.Range (0, toWrap.Rows).ToArray());
-		}
+	/// <inheritdoc/>
+	protected override void ToggleRow (int row)
+	{
+		var d = _toWrap.GetObjectOnRow (row);
+		_setter (d, !_getter (d));
+	}
 
-		/// <inheritdoc/>
-		protected override void ToggleRow (int row)
-		{
-			var d = toWrap.GetObjectOnRow (row);
-			setter (d, !getter(d));
-		}
-		
-		/// <inheritdoc/>
-		protected override void ToggleRows (int [] range)
-		{
-			// if all are ticked untick them
-			if (range.All (IsChecked)) {
-				// select none
-				foreach(var r in range) {
-					setter (toWrap.GetObjectOnRow (r), false);
-				}
-			} else {
-				// otherwise tick all
-				foreach (var r in range) {
-					setter (toWrap.GetObjectOnRow  (r), true);
-				}
+	/// <inheritdoc/>
+	protected override void ToggleRows (int [] range)
+	{
+		// if all are ticked untick them
+		if (range.All (IsChecked)) {
+			// select none
+			foreach (var r in range) {
+				_setter (_toWrap.GetObjectOnRow (r), false);
+			}
+		} else {
+			// otherwise tick all
+			foreach (var r in range) {
+				_setter (_toWrap.GetObjectOnRow (r), true);
 			}
 		}
+	}
 
-		/// <inheritdoc/>
-		protected override void ClearAllToggles ()
-		{
-			foreach (var e in toWrap.GetAllObjects()) {
-				setter (e, false);
-			}
+	/// <inheritdoc/>
+	protected override void ClearAllToggles ()
+	{
+		foreach (var e in _toWrap.GetAllObjects ()) {
+			_setter (e, false);
 		}
 	}
 }

+ 1 - 1
Terminal.Gui/Views/TableView/ColumnStyle.cs

@@ -6,7 +6,7 @@ namespace Terminal.Gui;
 /// Describes how to render a given column in  a <see cref="TableView"/> including <see cref="Alignment"/> 
 /// and textual representation of cells (e.g. date formats)
 /// 
-/// <a href="https://gui-cs.github.io/Terminal.Gui/docs/tableview.html">See TableView Deep Dive for more information</a>.
+/// <a href="../docs/tableview.md">See TableView Deep Dive for more information</a>.
 /// </summary>
 public class ColumnStyle {
 

+ 1 - 1
Terminal.Gui/Views/TableView/TableStyle.cs

@@ -5,7 +5,7 @@ namespace Terminal.Gui;
 /// <summary>
 /// Defines rendering options that affect how the table is displayed.
 /// 
-/// <a href="https://gui-cs.github.io/Terminal.Gui/docs/tableview.html">See TableView Deep Dive for more information</a>.
+/// <a href="../docs/tableview.md">See TableView Deep Dive for more information</a>.
 /// </summary>
 public class TableStyle {
 

+ 50 - 46
Terminal.Gui/Views/TableView/TableView.cs

@@ -24,7 +24,7 @@ namespace Terminal.Gui {
 	/// <summary>
 	/// View for tabular data based on a <see cref="ITableSource"/>.
 	/// 
-	/// <a href="https://gui-cs.github.io/Terminal.Gui/docs/tableview.html">See TableView Deep Dive for more information</a>.
+	/// <a href="../docs/tableview.md">See TableView Deep Dive for more information</a>.
 	/// </summary>
 	public class TableView : View {
 
@@ -34,7 +34,8 @@ namespace Terminal.Gui {
 		private int selectedColumn;
 		private ITableSource table;
 		private TableStyle style = new TableStyle ();
-		private Key cellActivationKey = Key.Enter;
+		// TODO: Update to use Key instead of KeyCode
+		private KeyCode cellActivationKey = KeyCode.Enter;
 
 		Point? scrollLeftPoint;
 		Point? scrollRightPoint;
@@ -169,18 +170,19 @@ namespace Terminal.Gui {
 		/// </summary>
 		public event EventHandler<CellToggledEventArgs> CellToggled;
 
+		// TODO: Update to use Key instead of KeyCode
 		/// <summary>
 		/// The key which when pressed should trigger <see cref="CellActivated"/> event.  Defaults to Enter.
 		/// </summary>
-		public Key CellActivationKey {
+		public KeyCode CellActivationKey {
 			get => cellActivationKey;
 			set {
 				if (cellActivationKey != value) {
-					ReplaceKeyBinding (cellActivationKey, value);
+					KeyBindings.Replace (cellActivationKey, value);
 
 					// of API user is mixing and matching old and new methods of keybinding then they may have lost
-					// the old binding (e.g. with ClearKeybindings) so ReplaceKeyBinding alone will fail
-					AddKeyBinding (value, Command.Accept);
+					// the old binding (e.g. with ClearKeybindings) so KeyBindings.Replace alone will fail
+					KeyBindings.Add (value, Command.Accept);
 					cellActivationKey = value;
 				}
 			}
@@ -239,30 +241,30 @@ namespace Terminal.Gui {
 			AddCommand (Command.ToggleChecked, () => { ToggleCurrentCellSelection (); return true; });
 
 			// Default keybindings for this view
-			AddKeyBinding (Key.CursorLeft, Command.Left);
-			AddKeyBinding (Key.CursorRight, Command.Right);
-			AddKeyBinding (Key.CursorUp, Command.LineUp);
-			AddKeyBinding (Key.CursorDown, Command.LineDown);
-			AddKeyBinding (Key.PageUp, Command.PageUp);
-			AddKeyBinding (Key.PageDown, Command.PageDown);
-			AddKeyBinding (Key.Home, Command.LeftHome);
-			AddKeyBinding (Key.End, Command.RightEnd);
-			AddKeyBinding (Key.Home | Key.CtrlMask, Command.TopHome);
-			AddKeyBinding (Key.End | Key.CtrlMask, Command.BottomEnd);
-
-			AddKeyBinding (Key.CursorLeft | Key.ShiftMask, Command.LeftExtend);
-			AddKeyBinding (Key.CursorRight | Key.ShiftMask, Command.RightExtend);
-			AddKeyBinding (Key.CursorUp | Key.ShiftMask, Command.LineUpExtend);
-			AddKeyBinding (Key.CursorDown | Key.ShiftMask, Command.LineDownExtend);
-			AddKeyBinding (Key.PageUp | Key.ShiftMask, Command.PageUpExtend);
-			AddKeyBinding (Key.PageDown | Key.ShiftMask, Command.PageDownExtend);
-			AddKeyBinding (Key.Home | Key.ShiftMask, Command.LeftHomeExtend);
-			AddKeyBinding (Key.End | Key.ShiftMask, Command.RightEndExtend);
-			AddKeyBinding (Key.Home | Key.CtrlMask | Key.ShiftMask, Command.TopHomeExtend);
-			AddKeyBinding (Key.End | Key.CtrlMask | Key.ShiftMask, Command.BottomEndExtend);
-
-			AddKeyBinding (Key.A | Key.CtrlMask, Command.SelectAll);
-			AddKeyBinding (CellActivationKey, Command.Accept);
+			KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
+			KeyBindings.Add (KeyCode.CursorRight, Command.Right);
+			KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
+			KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
+			KeyBindings.Add (KeyCode.PageUp, Command.PageUp);
+			KeyBindings.Add (KeyCode.PageDown, Command.PageDown);
+			KeyBindings.Add (KeyCode.Home, Command.LeftHome);
+			KeyBindings.Add (KeyCode.End, Command.RightEnd);
+			KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask, Command.TopHome);
+			KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask, Command.BottomEnd);
+
+			KeyBindings.Add (KeyCode.CursorLeft | KeyCode.ShiftMask, Command.LeftExtend);
+			KeyBindings.Add (KeyCode.CursorRight | KeyCode.ShiftMask, Command.RightExtend);
+			KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask, Command.LineUpExtend);
+			KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask, Command.LineDownExtend);
+			KeyBindings.Add (KeyCode.PageUp | KeyCode.ShiftMask, Command.PageUpExtend);
+			KeyBindings.Add (KeyCode.PageDown | KeyCode.ShiftMask, Command.PageDownExtend);
+			KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.LeftHomeExtend);
+			KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.RightEndExtend);
+			KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.TopHomeExtend);
+			KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.BottomEndExtend);
+
+			KeyBindings.Add (KeyCode.A | KeyCode.CtrlMask, Command.SelectAll);
+			KeyBindings.Add (CellActivationKey, Command.Accept);
 		}
 
 		///<inheritdoc/>
@@ -758,36 +760,29 @@ namespace Terminal.Gui {
 			return new string (representation.TakeWhile (c => (availableHorizontalSpace -= ((Rune)c).GetColumns ()) > 0).ToArray ());
 		}
 
-
-
 		/// <inheritdoc/>
-		public override bool ProcessKey (KeyEvent keyEvent)
+		public override bool OnProcessKeyDown (Key keyEvent)
 		{
 			if (TableIsNullOrInvisible ()) {
 				PositionCursor ();
 				return false;
 			}
 
-			var result = InvokeKeybindings (keyEvent);
-			if (result != null) {
-				PositionCursor ();
-				return true;
-			}
-
+		
 			if (CollectionNavigator != null &&
 				this.HasFocus &&
 				Table.Rows != 0 &&
 				Terminal.Gui.CollectionNavigator.IsCompatibleKey (keyEvent) &&
-				!keyEvent.Key.HasFlag (Key.CtrlMask) &&
-				!keyEvent.Key.HasFlag (Key.AltMask) &&
-				char.IsLetterOrDigit ((char)keyEvent.KeyValue)) {
+				!keyEvent.KeyCode.HasFlag (KeyCode.CtrlMask) &&
+				!keyEvent.KeyCode.HasFlag (KeyCode.AltMask) &&
+				Rune.IsLetterOrDigit ((Rune)keyEvent)) {
 				return CycleToNextTableEntryBeginningWith (keyEvent);
 			}
 
 			return false;
 		}
 
-		private bool CycleToNextTableEntryBeginningWith (KeyEvent keyEvent)
+		private bool CycleToNextTableEntryBeginningWith (Key keyEvent)
 		{
 			var row = SelectedRow;
 
@@ -796,7 +791,7 @@ namespace Terminal.Gui {
 				return false;
 			}
 
-			int match = CollectionNavigator.GetNextMatchingItem (row, (char)keyEvent.KeyValue);
+			int match = CollectionNavigator.GetNextMatchingItem (row, (char)keyEvent);
 
 			if (match != -1) {
 				SelectedRow = match;
@@ -1291,7 +1286,12 @@ namespace Terminal.Gui {
 		{
 			return ScreenToCell (clientX, clientY, out _, out _);
 		}
-		/// <inheritdoc cref="ScreenToCell(int, int)"/>
+
+		/// <summary>.
+		/// Returns the column and row of <see cref="Table"/> that corresponds to a given point 
+		/// on the screen (relative to the control client area).  Returns null if the point is
+		/// in the header, no table is loaded or outside the control bounds.
+		/// </summary>
 		/// <param name="clientX">X offset from the top left of the control.</param>
 		/// <param name="clientY">Y offset from the top left of the control.</param>
 		/// <param name="headerIfAny">If the click is in a header this is the column clicked.</param>
@@ -1300,7 +1300,11 @@ namespace Terminal.Gui {
 			return ScreenToCell (clientX, clientY, out headerIfAny, out _);
 		}
 
-		/// <inheritdoc cref="ScreenToCell(int, int)"/>
+		/// <summary>.
+		/// Returns the column and row of <see cref="Table"/> that corresponds to a given point 
+		/// on the screen (relative to the control client area).  Returns null if the point is
+		/// in the header, no table is loaded or outside the control bounds.
+		/// </summary>
 		/// <param name="clientX">X offset from the top left of the control.</param>
 		/// <param name="clientY">Y offset from the top left of the control.</param>
 		/// <param name="headerIfAny">If the click is in a header this is the column clicked.</param>

+ 5 - 5
Terminal.Gui/Views/TableView/TreeTableSource.cs

@@ -38,7 +38,7 @@ public class TreeTableSource<T> : IEnumerableTableSource<T>, IDisposable where T
 	{
 		_tableView = table;
 		_tree = tree;
-		_tableView.KeyPressed += Table_KeyPress;
+		_tableView.KeyDown += Table_KeyPress;
 		_tableView.MouseClick += Table_MouseClick;
 
 		var colList = subsequentColumns.Keys.ToList ();
@@ -68,7 +68,7 @@ public class TreeTableSource<T> : IEnumerableTableSource<T>, IDisposable where T
 	/// <inheritdoc/>
 	public void Dispose ()
 	{
-		_tableView.KeyPressed -= Table_KeyPress;
+		_tableView.KeyDown -= Table_KeyPress;
 		_tableView.MouseClick -= Table_MouseClick;
 		_tree.Dispose ();
 	}
@@ -106,7 +106,7 @@ public class TreeTableSource<T> : IEnumerableTableSource<T>, IDisposable where T
 		return sb.ToString ();
 	}
 
-	private void Table_KeyPress (object sender, KeyEventEventArgs e)
+	private void Table_KeyPress (object sender, Key e)
 	{
 		if (!IsInTreeColumn (_tableView.SelectedColumn, true)) {
 			return;
@@ -118,13 +118,13 @@ public class TreeTableSource<T> : IEnumerableTableSource<T>, IDisposable where T
 			return;
 		}
 
-		if (e.KeyEvent.Key == Key.CursorLeft) {
+		if (e.KeyCode == KeyCode.CursorLeft) {
 			if (_tree.IsExpanded (obj)) {
 				_tree.Collapse (obj);
 				e.Handled = true;
 			}
 		}
-		if (e.KeyEvent.Key == Key.CursorRight) {
+		if (e.KeyCode == KeyCode.CursorRight) {
 			if (_tree.CanExpand (obj) && !_tree.IsExpanded (obj)) {
 				_tree.Expand (obj);
 				e.Handled = true;

+ 200 - 196
Terminal.Gui/Views/TextField.cs

@@ -13,7 +13,6 @@ using System.Threading;
 using System.Text;
 using Terminal.Gui.Resources;
 
-
 namespace Terminal.Gui {
 	/// <summary>
 	///   Single-line text entry <see cref="View"/>
@@ -23,7 +22,7 @@ namespace Terminal.Gui {
 	/// </remarks>
 	public class TextField : View {
 		List<Rune> _text;
-		int _first, _point;
+		int _first, _cursorPosition;
 		int _selectedStart = -1; // -1 represents there is no text selection.
 		string _selectedText;
 		HistoryText _historyText = new HistoryText ();
@@ -58,14 +57,12 @@ namespace Terminal.Gui {
 		public event EventHandler<TextChangingEventArgs> TextChanging;
 
 		/// <summary>
-		///   Changed event, raised when the text has changed.
-		/// </summary>
+		/// Changed event, raised when the text has changed.
 		/// <remarks>
 		///   This event is raised when the <see cref="Text"/> changes. 
-		/// </remarks>
-		/// <remarks>
 		///   The passed <see cref="EventArgs"/> is a <see cref="string"/> containing the old value. 
 		/// </remarks>
+		/// </summary>
 		public event EventHandler<TextChangedEventArgs> TextChanged;
 
 		/// <summary>
@@ -102,8 +99,8 @@ namespace Terminal.Gui {
 				text = "";
 
 			this._text = text.Split ("\n") [0].EnumerateRunes ().ToList ();
-			_point = text.GetRuneCount ();
-			_first = _point > w + 1 ? _point - w + 1 : 0;
+			_cursorPosition = text.GetRuneCount ();
+			_first = _cursorPosition > w + 1 ? _cursorPosition - w + 1 : 0;
 			CanFocus = true;
 			Used = true;
 			WantMousePositionReports = true;
@@ -115,7 +112,7 @@ namespace Terminal.Gui {
 
 			// Things this view knows how to do
 			AddCommand (Command.DeleteCharRight, () => { DeleteCharRight (); return true; });
-			AddCommand (Command.DeleteCharLeft, () => { DeleteCharLeft (); return true; });
+			AddCommand (Command.DeleteCharLeft, () => { DeleteCharLeft (false); return true; });
 			AddCommand (Command.LeftHomeExtend, () => { MoveHomeExtend (); return true; });
 			AddCommand (Command.RightEndExtend, () => { MoveEndExtend (); return true; });
 			AddCommand (Command.LeftHome, () => { MoveHome (); return true; });
@@ -142,102 +139,103 @@ namespace Terminal.Gui {
 			AddCommand (Command.Paste, () => { Paste (); return true; });
 			AddCommand (Command.SelectAll, () => { SelectAll (); return true; });
 			AddCommand (Command.DeleteAll, () => { DeleteAll (); return true; });
-			AddCommand (Command.Accept, () => { ShowContextMenu (); return true; });
+			AddCommand (Command.ShowContextMenu, () => { ShowContextMenu (); return true; });
 
 			// Default keybindings for this view
-			AddKeyBinding (Key.DeleteChar, Command.DeleteCharRight);
-			AddKeyBinding (Key.D | Key.CtrlMask, Command.DeleteCharRight);
+			// We follow this as closely as possible: https://en.wikipedia.org/wiki/Table_of_keyboard_shortcuts
+			KeyBindings.Add (KeyCode.DeleteChar, Command.DeleteCharRight);
+			KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask, Command.DeleteCharRight);
 
-			AddKeyBinding (Key.Delete, Command.DeleteCharLeft);
-			AddKeyBinding (Key.Backspace, Command.DeleteCharLeft);
+			KeyBindings.Add (KeyCode.Delete, Command.DeleteCharLeft);
+			KeyBindings.Add (KeyCode.Backspace, Command.DeleteCharLeft);
 
-			AddKeyBinding (Key.Home | Key.ShiftMask, Command.LeftHomeExtend);
-			AddKeyBinding (Key.Home | Key.ShiftMask | Key.CtrlMask, Command.LeftHomeExtend);
-			AddKeyBinding (Key.A | Key.ShiftMask | Key.CtrlMask, Command.LeftHomeExtend);
+			KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.LeftHomeExtend);
+			KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.LeftHomeExtend);
+			KeyBindings.Add (KeyCode.A | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.LeftHomeExtend);
 
-			AddKeyBinding (Key.End | Key.ShiftMask, Command.RightEndExtend);
-			AddKeyBinding (Key.End | Key.ShiftMask | Key.CtrlMask, Command.RightEndExtend);
-			AddKeyBinding (Key.E | Key.ShiftMask | Key.CtrlMask, Command.RightEndExtend);
+			KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.RightEndExtend);
+			KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.RightEndExtend);
+			KeyBindings.Add (KeyCode.E | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.RightEndExtend);
 
-			AddKeyBinding (Key.Home, Command.LeftHome);
-			AddKeyBinding (Key.Home | Key.CtrlMask, Command.LeftHome);
-			AddKeyBinding (Key.A | Key.CtrlMask, Command.LeftHome);
+			KeyBindings.Add (KeyCode.Home, Command.LeftHome);
+			KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask, Command.LeftHome);
+			KeyBindings.Add (KeyCode.A | KeyCode.CtrlMask, Command.LeftHome);
 
-			AddKeyBinding (Key.CursorLeft | Key.ShiftMask, Command.LeftExtend);
-			AddKeyBinding (Key.CursorUp | Key.ShiftMask, Command.LeftExtend);
+			KeyBindings.Add (KeyCode.CursorLeft | KeyCode.ShiftMask, Command.LeftExtend);
+			KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask, Command.LeftExtend);
 
-			AddKeyBinding (Key.CursorRight | Key.ShiftMask, Command.RightExtend);
-			AddKeyBinding (Key.CursorDown | Key.ShiftMask, Command.RightExtend);
+			KeyBindings.Add (KeyCode.CursorRight | KeyCode.ShiftMask, Command.RightExtend);
+			KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask, Command.RightExtend);
 
-			AddKeyBinding (Key.CursorLeft | Key.ShiftMask | Key.CtrlMask, Command.WordLeftExtend);
-			AddKeyBinding (Key.CursorUp | Key.ShiftMask | Key.CtrlMask, Command.WordLeftExtend);
-			AddKeyBinding ((Key)((int)'B' + Key.ShiftMask | Key.AltMask), Command.WordLeftExtend);
+			KeyBindings.Add (KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordLeftExtend);
+			KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordLeftExtend);
+			KeyBindings.Add ((KeyCode)((int)'B' + KeyCode.ShiftMask | KeyCode.AltMask), Command.WordLeftExtend);
 
-			AddKeyBinding (Key.CursorRight | Key.ShiftMask | Key.CtrlMask, Command.WordRightExtend);
-			AddKeyBinding (Key.CursorDown | Key.ShiftMask | Key.CtrlMask, Command.WordRightExtend);
-			AddKeyBinding ((Key)((int)'F' + Key.ShiftMask | Key.AltMask), Command.WordRightExtend);
+			KeyBindings.Add (KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordRightExtend);
+			KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordRightExtend);
+			KeyBindings.Add ((KeyCode)((int)'F' + KeyCode.ShiftMask | KeyCode.AltMask), Command.WordRightExtend);
 
-			AddKeyBinding (Key.CursorLeft, Command.Left);
-			AddKeyBinding (Key.B | Key.CtrlMask, Command.Left);
+			KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
+			KeyBindings.Add (KeyCode.B | KeyCode.CtrlMask, Command.Left);
 
-			AddKeyBinding (Key.End, Command.RightEnd);
-			AddKeyBinding (Key.End | Key.CtrlMask, Command.RightEnd);
-			AddKeyBinding (Key.E | Key.CtrlMask, Command.RightEnd);
+			KeyBindings.Add (KeyCode.End, Command.RightEnd);
+			KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask, Command.RightEnd);
+			KeyBindings.Add (KeyCode.E | KeyCode.CtrlMask, Command.RightEnd);
 
-			AddKeyBinding (Key.CursorRight, Command.Right);
-			AddKeyBinding (Key.F | Key.CtrlMask, Command.Right);
+			KeyBindings.Add (KeyCode.CursorRight, Command.Right);
+			KeyBindings.Add (KeyCode.F | KeyCode.CtrlMask, Command.Right);
 
-			AddKeyBinding (Key.K | Key.CtrlMask, Command.CutToEndLine);
-			AddKeyBinding (Key.K | Key.AltMask, Command.CutToStartLine);
+			KeyBindings.Add (KeyCode.K | KeyCode.CtrlMask, Command.CutToEndLine);
+			KeyBindings.Add (KeyCode.K | KeyCode.AltMask, Command.CutToStartLine);
 
-			AddKeyBinding (Key.Z | Key.CtrlMask, Command.Undo);
-			AddKeyBinding (Key.Backspace | Key.AltMask, Command.Undo);
+			KeyBindings.Add (KeyCode.Z | KeyCode.CtrlMask, Command.Undo);
+			KeyBindings.Add (KeyCode.Backspace | KeyCode.AltMask, Command.Undo);
 
-			AddKeyBinding (Key.Y | Key.CtrlMask, Command.Redo);
+			KeyBindings.Add (KeyCode.Y | KeyCode.CtrlMask, Command.Redo);
 
-			AddKeyBinding (Key.CursorLeft | Key.CtrlMask, Command.WordLeft);
-			AddKeyBinding (Key.CursorUp | Key.CtrlMask, Command.WordLeft);
-			AddKeyBinding ((Key)((int)'B' + Key.AltMask), Command.WordLeft);
+			KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask, Command.WordLeft);
+			KeyBindings.Add (KeyCode.CursorUp | KeyCode.CtrlMask, Command.WordLeft);
+			KeyBindings.Add ((KeyCode)((int)'B' + KeyCode.AltMask), Command.WordLeft);
 
-			AddKeyBinding (Key.CursorRight | Key.CtrlMask, Command.WordRight);
-			AddKeyBinding (Key.CursorDown | Key.CtrlMask, Command.WordRight);
-			AddKeyBinding ((Key)((int)'F' + Key.AltMask), Command.WordRight);
+			KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask, Command.WordRight);
+			KeyBindings.Add (KeyCode.CursorDown | KeyCode.CtrlMask, Command.WordRight);
+			KeyBindings.Add ((KeyCode)((int)'F' + KeyCode.AltMask), Command.WordRight);
 
-			AddKeyBinding (Key.DeleteChar | Key.CtrlMask, Command.KillWordForwards);
-			AddKeyBinding (Key.Backspace | Key.CtrlMask, Command.KillWordBackwards);
-			AddKeyBinding (Key.InsertChar, Command.ToggleOverwrite);
-			AddKeyBinding (Key.C | Key.CtrlMask, Command.Copy);
-			AddKeyBinding (Key.X | Key.CtrlMask, Command.Cut);
-			AddKeyBinding (Key.V | Key.CtrlMask, Command.Paste);
-			AddKeyBinding (Key.T | Key.CtrlMask, Command.SelectAll);
+			KeyBindings.Add (KeyCode.DeleteChar | KeyCode.CtrlMask, Command.KillWordForwards);
+			KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask, Command.KillWordBackwards);
+			KeyBindings.Add (KeyCode.InsertChar, Command.ToggleOverwrite);
+			KeyBindings.Add (KeyCode.C | KeyCode.CtrlMask, Command.Copy);
+			KeyBindings.Add (KeyCode.X | KeyCode.CtrlMask, Command.Cut);
+			KeyBindings.Add (KeyCode.V | KeyCode.CtrlMask, Command.Paste);
+			KeyBindings.Add (KeyCode.T | KeyCode.CtrlMask, Command.SelectAll);
 
-			AddKeyBinding (Key.R | Key.CtrlMask, Command.DeleteAll);
-			AddKeyBinding (Key.D | Key.CtrlMask | Key.ShiftMask, Command.DeleteAll);
+			KeyBindings.Add (KeyCode.R | KeyCode.CtrlMask, Command.DeleteAll);
+			KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.DeleteAll);
 
 			_currentCulture = Thread.CurrentThread.CurrentUICulture;
 
 			ContextMenu = new ContextMenu (this, BuildContextMenuBarItem ());
 			ContextMenu.KeyChanged += ContextMenu_KeyChanged;
 
-			AddKeyBinding (ContextMenu.Key, Command.Accept);
+			KeyBindings.Add (ContextMenu.Key.KeyCode, KeyBindingScope.HotKey, Command.ShowContextMenu);
 		}
 
 		private MenuBarItem BuildContextMenuBarItem ()
 		{
 			return new MenuBarItem (new MenuItem [] {
-					new MenuItem (Strings.ctxSelectAll, "", () => SelectAll (), null, null, GetKeyFromCommand (Command.SelectAll)),
-					new MenuItem (Strings.ctxDeleteAll, "", () => DeleteAll (), null, null, GetKeyFromCommand (Command.DeleteAll)),
-					new MenuItem (Strings.ctxCopy, "", () => Copy (), null, null, GetKeyFromCommand (Command.Copy)),
-					new MenuItem (Strings.ctxCut, "", () => Cut (), null, null, GetKeyFromCommand (Command.Cut)),
-					new MenuItem (Strings.ctxPaste, "", () => Paste (), null, null, GetKeyFromCommand (Command.Paste)),
-					new MenuItem (Strings.ctxUndo, "", () => Undo (), null, null, GetKeyFromCommand (Command.Undo)),
-					new MenuItem (Strings.ctxRedo, "", () => Redo (), null, null, GetKeyFromCommand (Command.Redo)),
+					new MenuItem (Strings.ctxSelectAll, "", () => SelectAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.SelectAll)),
+					new MenuItem (Strings.ctxDeleteAll, "", () => DeleteAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.DeleteAll)),
+					new MenuItem (Strings.ctxCopy, "", () => Copy (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Copy)),
+					new MenuItem (Strings.ctxCut, "", () => Cut (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Cut)),
+					new MenuItem (Strings.ctxPaste, "", () => Paste (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Paste)),
+					new MenuItem (Strings.ctxUndo, "", () => Undo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Undo)),
+					new MenuItem (Strings.ctxRedo, "", () => Redo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Redo)),
 				});
 		}
 
 		private void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e)
 		{
-			ReplaceKeyBinding (e.OldKey, e.NewKey);
+			KeyBindings.Replace (e.OldKey.KeyCode, e.NewKey.KeyCode);
 		}
 
 		private void HistoryText_ChangeText (object sender, HistoryText.HistoryTextItem obj)
@@ -300,8 +298,6 @@ namespace Terminal.Gui {
 		/// <summary>
 		///   Sets or gets the text held by the view.
 		/// </summary>
-		/// <remarks>
-		/// </remarks>
 		public new string Text {
 			get {
 				return StringExtensions.ToString (_text);
@@ -315,8 +311,8 @@ namespace Terminal.Gui {
 
 				var newText = OnTextChanging (value.Replace ("\t", "").Split ("\n") [0]);
 				if (newText.Cancel) {
-					if (_point > _text.Count) {
-						_point = _text.Count;
+					if (_cursorPosition > _text.Count) {
+						_cursorPosition = _text.Count;
 					}
 					return;
 				}
@@ -325,8 +321,8 @@ namespace Terminal.Gui {
 
 				if (!Secret && !_historyText.IsFromHistory) {
 					_historyText.Add (new List<List<RuneCell>> () { TextModel.ToRuneCellList (oldText) },
-						new Point (_point, 0));
-					_historyText.Add (new List<List<RuneCell>> () { TextModel.ToRuneCells (_text) }, new Point (_point, 0)
+						new Point (_cursorPosition, 0));
+					_historyText.Add (new List<List<RuneCell>> () { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0)
 						, HistoryText.LineStatus.Replaced);
 				}
 
@@ -334,8 +330,8 @@ namespace Terminal.Gui {
 
 				ProcessAutocomplete ();
 
-				if (_point > _text.Count) {
-					_point = Math.Max (TextModel.DisplaySize (_text, 0).size - 1, 0);
+				if (_cursorPosition > _text.Count) {
+					_cursorPosition = Math.Max (TextModel.DisplaySize (_text, 0).size - 1, 0);
 				}
 
 				Adjust ();
@@ -345,26 +341,26 @@ namespace Terminal.Gui {
 
 		/// <summary>
 		///   Sets the secret property.
-		/// </summary>
 		/// <remarks>
 		///   This makes the text entry suitable for entering passwords.
 		/// </remarks>
+		/// </summary>
 		public bool Secret { get; set; }
 
 		/// <summary>
 		///    Sets or gets the current cursor position.
 		/// </summary>
 		public virtual int CursorPosition {
-			get { return _point; }
+			get { return _cursorPosition; }
 			set {
 				if (value < 0) {
-					_point = 0;
+					_cursorPosition = 0;
 				} else if (value > _text.Count) {
-					_point = _text.Count;
+					_cursorPosition = _text.Count;
 				} else {
-					_point = value;
+					_cursorPosition = value;
 				}
-				PrepareSelection (_selectedStart, _point - _selectedStart);
+				PrepareSelection (_selectedStart, _cursorPosition - _selectedStart);
 			}
 		}
 
@@ -399,12 +395,12 @@ namespace Terminal.Gui {
 
 			var col = 0;
 			for (int idx = _first < 0 ? 0 : _first; idx < _text.Count; idx++) {
-				if (idx == _point)
+				if (idx == _cursorPosition)
 					break;
 				var cols = _text [idx].GetColumns ();
 				TextModel.SetCol (ref col, Frame.Width - 1, cols);
 			}
-			var pos = _point - _first + Math.Min (Frame.X, 0);
+			var pos = _cursorPosition - _first + Math.Min (Frame.X, 0);
 			var offB = OffSetBackground ();
 			var containerFrame = SuperView?.BoundsToScreen (SuperView.Bounds) ?? default;
 			var thisFrame = BoundsToScreen (Bounds);
@@ -462,7 +458,7 @@ namespace Terminal.Gui {
 			for (int idx = p; idx < tcount; idx++) {
 				var rune = _text [idx];
 				var cols = rune.GetColumns ();
-				if (idx == _point && HasFocus && !Used && _length == 0 && !ReadOnly) {
+				if (idx == _cursorPosition && HasFocus && !Used && _length == 0 && !ReadOnly) {
 					Driver.SetAttribute (selColor);
 				} else if (ReadOnly) {
 					Driver.SetAttribute (idx >= _start && _length > 0 && idx < _start + _length ? selColor : roc);
@@ -563,19 +559,20 @@ namespace Terminal.Gui {
 
 		void Adjust ()
 		{
-			if (!IsAdded)
+			if (!IsAdded) {
 				return;
+			}
 
 			int offB = OffSetBackground ();
 			bool need = NeedsDisplay || !Used;
-			if (_point < _first) {
-				_first = _point;
+			if (_cursorPosition < _first) {
+				_first = _cursorPosition;
 				need = true;
-			} else if (Frame.Width > 0 && (_first + _point - (Frame.Width + offB) == 0 ||
-				  TextModel.DisplaySize (_text, _first, _point).size >= Frame.Width + offB)) {
+			} else if (Frame.Width > 0 && (_first + _cursorPosition - (Frame.Width + offB) == 0 ||
+				  TextModel.DisplaySize (_text, _first, _cursorPosition).size >= Frame.Width + offB)) {
 
 				_first = Math.Max (TextModel.CalculateLeftColumn (_text, _first,
-					_point, Frame.Width + offB), 0);
+					_cursorPosition, Frame.Width + offB), 0);
 				need = true;
 			}
 			if (need) {
@@ -617,13 +614,21 @@ namespace Terminal.Gui {
 				Clipboard.Contents = StringExtensions.ToString (text.ToList ());
 		}
 
-		int _oldCursorPos;
+		int _preTextChangedCursorPos;
 
+		///<inheritdoc/>
+		public override bool? OnInvokingKeyBindings (Key a)
+		{
+			// Give autocomplete first opportunity to respond to key presses
+			if (SelectedLength == 0 && Autocomplete.Suggestions.Count > 0 && Autocomplete.ProcessKey (a)) {
+				return true;
+			}
+			return base.OnInvokingKeyBindings (a);
+		}
+
+		/// TODO: Flush out these docs
 		/// <summary>
 		/// Processes key presses for the <see cref="TextField"/>.
-		/// </summary>
-		/// <param name="kb"></param>
-		/// <returns></returns>
 		/// <remarks>
 		/// The <see cref="TextField"/> control responds to the following keys:
 		/// <list type="table">
@@ -637,61 +642,56 @@ namespace Terminal.Gui {
 		///    </item>
 		/// </list>
 		/// </remarks>
-		public override bool ProcessKey (KeyEvent kb)
+		/// </summary>
+		/// <param name="a"></param>
+		/// <returns></returns>
+		public override bool OnProcessKeyDown (Key a)
 		{
-			// remember current cursor position
-			// because the new calculated cursor position is needed to be set BEFORE the change event is triggest
+			// Remember the cursor position because the new calculated cursor position is needed
+			// to be set BEFORE the TextChanged event is triggered.
 			// Needed for the Elmish Wrapper issue https://github.com/DieselMeister/Terminal.Gui.Elmish/issues/2
-			_oldCursorPos = _point;
-
-			// Give autocomplete first opportunity to respond to key presses
-			if (SelectedLength == 0 && Autocomplete.Suggestions.Count > 0 && Autocomplete.ProcessKey (kb)) {
-				return true;
-			}
-
-			var result = InvokeKeybindings (new KeyEvent (ShortcutHelper.GetModifiersKey (kb),
-				new KeyModifiers () { Alt = kb.IsAlt, Ctrl = kb.IsCtrl, Shift = kb.IsShift }));
-			if (result != null)
-				return (bool)result;
+			_preTextChangedCursorPos = _cursorPosition;
 
 			// Ignore other control characters.
-			if (kb.Key < Key.Space || kb.Key > Key.CharMask)
+			if (!a.IsKeyCodeAtoZ && (a.KeyCode < KeyCode.Space || a.KeyCode > KeyCode.CharMask)) {
 				return false;
+			}
 
-			if (ReadOnly)
+			if (ReadOnly) {
 				return true;
+			}
 
-			InsertText (kb);
+			InsertText (a, true);
 
 			return true;
 		}
 
-		void InsertText (KeyEvent kb, bool useOldCursorPos = true)
+		void InsertText (Key a, bool usePreTextChangedCursorPos)
 		{
-			_historyText.Add (new List<List<RuneCell>> () { TextModel.ToRuneCells (_text) }, new Point (_point, 0));
+			_historyText.Add (new List<List<RuneCell>> () { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0));
 
 			List<Rune> newText = _text;
 			if (_length > 0) {
 				newText = DeleteSelectedText ();
-				_oldCursorPos = _point;
+				_preTextChangedCursorPos = _cursorPosition;
 			}
-			if (!useOldCursorPos) {
-				_oldCursorPos = _point;
+			if (!usePreTextChangedCursorPos) {
+				_preTextChangedCursorPos = _cursorPosition;
 			}
-			var kbstr = ((Rune)(uint)kb.Key).ToString ().EnumerateRunes ();
+			var kbstr = a.AsRune.ToString ().EnumerateRunes ();
 			if (Used) {
-				_point++;
-				if (_point == newText.Count + 1) {
+				_cursorPosition++;
+				if (_cursorPosition == newText.Count + 1) {
 					SetText (newText.Concat (kbstr).ToList ());
 				} else {
-					if (_oldCursorPos > newText.Count) {
-						_oldCursorPos = newText.Count;
+					if (_preTextChangedCursorPos > newText.Count) {
+						_preTextChangedCursorPos = newText.Count;
 					}
-					SetText (newText.GetRange (0, _oldCursorPos).Concat (kbstr).Concat (newText.GetRange (_oldCursorPos, Math.Min (newText.Count - _oldCursorPos, newText.Count))));
+					SetText (newText.GetRange (0, _preTextChangedCursorPos).Concat (kbstr).Concat (newText.GetRange (_preTextChangedCursorPos, Math.Min (newText.Count - _preTextChangedCursorPos, newText.Count))));
 				}
 			} else {
-				SetText (newText.GetRange (0, _oldCursorPos).Concat (kbstr).Concat (newText.GetRange (Math.Min (_oldCursorPos + 1, newText.Count), Math.Max (newText.Count - _oldCursorPos - 1, 0))));
-				_point++;
+				SetText (newText.GetRange (0, _preTextChangedCursorPos).Concat (kbstr).Concat (newText.GetRange (Math.Min (_preTextChangedCursorPos + 1, newText.Count), Math.Max (newText.Count - _preTextChangedCursorPos - 1, 0))));
+				_cursorPosition++;
 			}
 			Adjust ();
 		}
@@ -715,11 +715,11 @@ namespace Terminal.Gui {
 		public virtual void KillWordBackwards ()
 		{
 			ClearAllSelection ();
-			var newPos = GetModel ().WordBackward (_point, 0);
+			var newPos = GetModel ().WordBackward (_cursorPosition, 0);
 			if (newPos == null) return;
 			if (newPos.Value.col != -1) {
-				SetText (_text.GetRange (0, newPos.Value.col).Concat (_text.GetRange (_point, _text.Count - _point)));
-				_point = newPos.Value.col;
+				SetText (_text.GetRange (0, newPos.Value.col).Concat (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition)));
+				_cursorPosition = newPos.Value.col;
 			}
 			Adjust ();
 		}
@@ -730,10 +730,10 @@ namespace Terminal.Gui {
 		public virtual void KillWordForwards ()
 		{
 			ClearAllSelection ();
-			var newPos = GetModel ().WordForward (_point, 0);
+			var newPos = GetModel ().WordForward (_cursorPosition, 0);
 			if (newPos == null) return;
 			if (newPos.Value.col != -1) {
-				SetText (_text.GetRange (0, _point).Concat (_text.GetRange (newPos.Value.col, _text.Count - newPos.Value.col)));
+				SetText (_text.GetRange (0, _cursorPosition).Concat (_text.GetRange (newPos.Value.col, _text.Count - newPos.Value.col)));
 			}
 			Adjust ();
 		}
@@ -741,20 +741,20 @@ namespace Terminal.Gui {
 		void MoveWordRight ()
 		{
 			ClearAllSelection ();
-			var newPos = GetModel ().WordForward (_point, 0);
+			var newPos = GetModel ().WordForward (_cursorPosition, 0);
 			if (newPos == null) return;
 			if (newPos.Value.col != -1)
-				_point = newPos.Value.col;
+				_cursorPosition = newPos.Value.col;
 			Adjust ();
 		}
 
 		void MoveWordLeft ()
 		{
 			ClearAllSelection ();
-			var newPos = GetModel ().WordBackward (_point, 0);
+			var newPos = GetModel ().WordBackward (_cursorPosition, 0);
 			if (newPos == null) return;
 			if (newPos.Value.col != -1)
-				_point = newPos.Value.col;
+				_cursorPosition = newPos.Value.col;
 			Adjust ();
 		}
 
@@ -803,11 +803,11 @@ namespace Terminal.Gui {
 				return;
 
 			ClearAllSelection ();
-			if (_point == 0)
+			if (_cursorPosition == 0)
 				return;
-			SetClipboard (_text.GetRange (0, _point));
-			SetText (_text.GetRange (_point, _text.Count - _point));
-			_point = 0;
+			SetClipboard (_text.GetRange (0, _cursorPosition));
+			SetText (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition));
+			_cursorPosition = 0;
 			Adjust ();
 		}
 
@@ -817,19 +817,19 @@ namespace Terminal.Gui {
 				return;
 
 			ClearAllSelection ();
-			if (_point >= _text.Count)
+			if (_cursorPosition >= _text.Count)
 				return;
-			SetClipboard (_text.GetRange (_point, _text.Count - _point));
-			SetText (_text.GetRange (0, _point));
+			SetClipboard (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition));
+			SetText (_text.GetRange (0, _cursorPosition));
 			Adjust ();
 		}
 
 		void MoveRight ()
 		{
 			ClearAllSelection ();
-			if (_point == _text.Count)
+			if (_cursorPosition == _text.Count)
 				return;
-			_point++;
+			_cursorPosition++;
 			Adjust ();
 		}
 
@@ -839,40 +839,40 @@ namespace Terminal.Gui {
 		public void MoveEnd ()
 		{
 			ClearAllSelection ();
-			_point = _text.Count;
+			_cursorPosition = _text.Count;
 			Adjust ();
 		}
 
 		void MoveLeft ()
 		{
 			ClearAllSelection ();
-			if (_point > 0) {
-				_point--;
+			if (_cursorPosition > 0) {
+				_cursorPosition--;
 				Adjust ();
 			}
 		}
 
 		void MoveWordRightExtend ()
 		{
-			if (_point < _text.Count) {
-				int x = _start > -1 && _start > _point ? _start : _point;
+			if (_cursorPosition < _text.Count) {
+				int x = _start > -1 && _start > _cursorPosition ? _start : _cursorPosition;
 				var newPos = GetModel ().WordForward (x, 0);
 				if (newPos == null) return;
 				if (newPos.Value.col != -1)
-					_point = newPos.Value.col;
+					_cursorPosition = newPos.Value.col;
 				PrepareSelection (x, newPos.Value.col - x);
 			}
 		}
 
 		void MoveWordLeftExtend ()
 		{
-			if (_point > 0) {
-				int x = Math.Min (_start > -1 && _start > _point ? _start : _point, _text.Count);
+			if (_cursorPosition > 0) {
+				int x = Math.Min (_start > -1 && _start > _cursorPosition ? _start : _cursorPosition, _text.Count);
 				if (x > 0) {
 					var newPos = GetModel ().WordBackward (x, 0);
 					if (newPos == null) return;
 					if (newPos.Value.col != -1)
-						_point = newPos.Value.col;
+						_cursorPosition = newPos.Value.col;
 					PrepareSelection (x, newPos.Value.col - x);
 				}
 			}
@@ -880,65 +880,68 @@ namespace Terminal.Gui {
 
 		void MoveRightExtend ()
 		{
-			if (_point < _text.Count) {
-				PrepareSelection (_point++, 1);
+			if (_cursorPosition < _text.Count) {
+				PrepareSelection (_cursorPosition++, 1);
 			}
 		}
 
 		void MoveLeftExtend ()
 		{
-			if (_point > 0) {
-				PrepareSelection (_point--, -1);
+			if (_cursorPosition > 0) {
+				PrepareSelection (_cursorPosition--, -1);
 			}
 		}
 
 		void MoveHome ()
 		{
 			ClearAllSelection ();
-			_point = 0;
+			_cursorPosition = 0;
 			Adjust ();
 		}
 
 		void MoveEndExtend ()
 		{
-			if (_point <= _text.Count) {
-				int x = _point;
-				_point = _text.Count;
-				PrepareSelection (x, _point - x);
+			if (_cursorPosition <= _text.Count) {
+				int x = _cursorPosition;
+				_cursorPosition = _text.Count;
+				PrepareSelection (x, _cursorPosition - x);
 			}
 		}
 
 		void MoveHomeExtend ()
 		{
-			if (_point > 0) {
-				int x = _point;
-				_point = 0;
-				PrepareSelection (x, _point - x);
+			if (_cursorPosition > 0) {
+				int x = _cursorPosition;
+				_cursorPosition = 0;
+				PrepareSelection (x, _cursorPosition - x);
 			}
 		}
 
 		/// <summary>
-		/// Deletes the left character.
+		/// Deletes the character to the left.
 		/// </summary>
-		public virtual void DeleteCharLeft (bool useOldCursorPos = true)
+		/// <param name="usePreTextChangedCursorPos">If set to <see langword="true">true</see> use the cursor position cached
+		/// ; otherwise use <see cref="CursorPosition"/>.
+		/// use .</param>
+		public virtual void DeleteCharLeft (bool usePreTextChangedCursorPos)
 		{
 			if (ReadOnly)
 				return;
 
-			_historyText.Add (new List<List<RuneCell>> () { TextModel.ToRuneCells (_text) }, new Point (_point, 0));
+			_historyText.Add (new List<List<RuneCell>> () { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0));
 
 			if (_length == 0) {
-				if (_point == 0)
+				if (_cursorPosition == 0)
 					return;
 
-				if (!useOldCursorPos) {
-					_oldCursorPos = _point;
+				if (!usePreTextChangedCursorPos) {
+					_preTextChangedCursorPos = _cursorPosition;
 				}
-				_point--;
-				if (_oldCursorPos < _text.Count) {
-					SetText (_text.GetRange (0, _oldCursorPos - 1).Concat (_text.GetRange (_oldCursorPos, _text.Count - _oldCursorPos)));
+				_cursorPosition--;
+				if (_preTextChangedCursorPos < _text.Count) {
+					SetText (_text.GetRange (0, _preTextChangedCursorPos - 1).Concat (_text.GetRange (_preTextChangedCursorPos, _text.Count - _preTextChangedCursorPos)));
 				} else {
-					SetText (_text.GetRange (0, _oldCursorPos - 1));
+					SetText (_text.GetRange (0, _preTextChangedCursorPos - 1));
 				}
 				Adjust ();
 			} else {
@@ -949,20 +952,20 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Deletes the right character.
+		/// Deletes the character to the right.
 		/// </summary>
 		public virtual void DeleteCharRight ()
 		{
 			if (ReadOnly)
 				return;
 
-			_historyText.Add (new List<List<RuneCell>> () { TextModel.ToRuneCells (_text) }, new Point (_point, 0));
+			_historyText.Add (new List<List<RuneCell>> () { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0));
 
 			if (_length == 0) {
-				if (_text.Count == 0 || _text.Count == _point)
+				if (_text.Count == 0 || _text.Count == _cursorPosition)
 					return;
 
-				SetText (_text.GetRange (0, _point).Concat (_text.GetRange (_point + 1, _text.Count - (_point + 1))));
+				SetText (_text.GetRange (0, _cursorPosition).Concat (_text.GetRange (_cursorPosition + 1, _text.Count - (_cursorPosition + 1))));
 				Adjust ();
 			} else {
 				var newText = DeleteSelectedText ();
@@ -1007,7 +1010,7 @@ namespace Terminal.Gui {
 
 			_selectedStart = 0;
 			MoveEndExtend ();
-			DeleteCharLeft ();
+			DeleteCharLeft (false);
 			SetNeedsDisplay ();
 		}
 
@@ -1024,7 +1027,7 @@ namespace Terminal.Gui {
 				} else {
 					_selectedStart = value;
 				}
-				PrepareSelection (_selectedStart, _point - _selectedStart);
+				PrepareSelection (_selectedStart, _cursorPosition - _selectedStart);
 			}
 		}
 
@@ -1105,7 +1108,7 @@ namespace Terminal.Gui {
 				if (newPosFw == null) return true;
 				ClearAllSelection ();
 				if (newPosFw.Value.col != -1 && sbw != -1) {
-					_point = newPosFw.Value.col;
+					_cursorPosition = newPosFw.Value.col;
 				}
 				PrepareSelection (sbw, newPosFw.Value.col - sbw);
 			} else if (ev.Flags == MouseFlags.Button1TripleClicked) {
@@ -1148,14 +1151,14 @@ namespace Terminal.Gui {
 				pX = TextModel.GetColFromX (_text, _first, x);
 			}
 			if (_first + pX > _text.Count) {
-				_point = _text.Count;
+				_cursorPosition = _text.Count;
 			} else if (_first + pX < _first) {
-				_point = 0;
+				_cursorPosition = 0;
 			} else {
-				_point = _first + pX;
+				_cursorPosition = _first + pX;
 			}
 
-			return _point;
+			return _cursorPosition;
 		}
 
 		void PrepareSelection (int x, int direction = 0)
@@ -1174,6 +1177,7 @@ namespace Terminal.Gui {
 				} else if (_start > -1 && _length == 0) {
 					_selectedText = null;
 				}
+				SetNeedsDisplay ();
 			} else if (_length > 0 || _selectedText != null) {
 				ClearAllSelection ();
 			}
@@ -1199,8 +1203,8 @@ namespace Terminal.Gui {
 
 		void SetSelectedStartSelectedLength ()
 		{
-			if (SelectedStart > -1 && _point < SelectedStart) {
-				_start = _point;
+			if (SelectedStart > -1 && _cursorPosition < SelectedStart) {
+				_start = _cursorPosition;
 			} else {
 				_start = SelectedStart;
 			}
@@ -1234,12 +1238,12 @@ namespace Terminal.Gui {
 		List<Rune> DeleteSelectedText ()
 		{
 			SetSelectedStartSelectedLength ();
-			int selStart = SelectedStart > -1 ? _start : _point;
+			int selStart = SelectedStart > -1 ? _start : _cursorPosition;
 			var newText = StringExtensions.ToString (_text.GetRange (0, selStart)) +
 				 StringExtensions.ToString (_text.GetRange (selStart + _length, _text.Count - (selStart + _length)));
 
 			ClearAllSelection ();
-			_point = selStart >= newText.GetRuneCount () ? newText.GetRuneCount () : selStart;
+			_cursorPosition = selStart >= newText.GetRuneCount () ? newText.GetRuneCount () : selStart;
 			return newText.ToRuneList ();
 		}
 
@@ -1259,7 +1263,7 @@ namespace Terminal.Gui {
 				cbTxt +
 				StringExtensions.ToString (_text.GetRange (selStart + _length, _text.Count - (selStart + _length)));
 
-			_point = selStart + cbTxt.GetRuneCount ();
+			_cursorPosition = selStart + cbTxt.GetRuneCount ();
 			ClearAllSelection ();
 			SetNeedsDisplay ();
 			Adjust ();
@@ -1298,21 +1302,21 @@ namespace Terminal.Gui {
 		/// exactly as if the user had just typed it
 		/// </summary>
 		/// <param name="toAdd">Text to add</param>
-		/// <param name="useOldCursorPos">If uses the <see cref="_oldCursorPos"/>.</param>
+		/// <param name="useOldCursorPos">Use the previous cursor position.</param>
 		public void InsertText (string toAdd, bool useOldCursorPos = true)
 		{
 			foreach (var ch in toAdd) {
 
-				Key key;
+				KeyCode key;
 
 				try {
-					key = (Key)ch;
+					key = (KeyCode)ch;
 				} catch (Exception) {
 
 					throw new ArgumentException ($"Cannot insert character '{ch}' because it does not map to a Key");
 				}
 
-				InsertText (new KeyEvent () { Key = key }, useOldCursorPos);
+				InsertText (new Key () { KeyCode = key }, useOldCursorPos);
 			}
 		}
 

+ 13 - 16
Terminal.Gui/Views/TextValidateField.cs

@@ -400,15 +400,15 @@ namespace Terminal.Gui {
 			AddCommand (Command.Right, () => { CursorRight (); return true; });
 
 			// Default keybindings for this view
-			AddKeyBinding (Key.Home, Command.LeftHome);
-			AddKeyBinding (Key.End, Command.RightEnd);
+			KeyBindings.Add (KeyCode.Home, Command.LeftHome);
+			KeyBindings.Add (KeyCode.End, Command.RightEnd);
 
-			AddKeyBinding (Key.Delete, Command.DeleteCharRight);
-			AddKeyBinding (Key.DeleteChar, Command.DeleteCharRight);
+			KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight);
+			KeyBindings.Add (KeyCode.DeleteChar, Command.DeleteCharRight);
 
-			AddKeyBinding (Key.Backspace, Command.DeleteCharLeft);
-			AddKeyBinding (Key.CursorLeft, Command.Left);
-			AddKeyBinding (Key.CursorRight, Command.Right);
+			KeyBindings.Add (KeyCode.Backspace, Command.DeleteCharLeft);
+			KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
+			KeyBindings.Add (KeyCode.CursorRight, Command.Right);
 		}
 
 		/// <summary>
@@ -612,20 +612,17 @@ namespace Terminal.Gui {
 		}
 
 		///<inheritdoc/>
-		public override bool ProcessKey (KeyEvent kb)
+		public override bool OnProcessKeyDown (Key a)
 		{
 			if (provider == null) {
 				return false;
 			}
 
-			var result = InvokeKeybindings (kb);
-			if (result != null)
-				return (bool)result;
-
-			if (kb.Key < Key.Space || kb.Key > Key.CharMask)
+			if (a.AsRune == default) {
 				return false;
-
-			var key = new Rune ((uint)kb.KeyValue);
+			}
+			
+			var key = a.AsRune;
 
 			var inserted = provider.InsertAt ((char)key.Value, cursorPosition);
 
@@ -633,7 +630,7 @@ namespace Terminal.Gui {
 				CursorRight ();
 			}
 
-			return true;
+			return false;
 		}
 
 		/// <summary>

+ 109 - 103
Terminal.Gui/Views/TextView.cs

@@ -1673,126 +1673,127 @@ namespace Terminal.Gui {
 			});
 
 			// Default keybindings for this view
-			AddKeyBinding (Key.PageDown, Command.PageDown);
-			AddKeyBinding (Key.V | Key.CtrlMask, Command.PageDown);
+			KeyBindings.Add (KeyCode.PageDown, Command.PageDown);
+			KeyBindings.Add (KeyCode.V | KeyCode.CtrlMask, Command.PageDown);
 
-			AddKeyBinding (Key.PageDown | Key.ShiftMask, Command.PageDownExtend);
+			KeyBindings.Add (KeyCode.PageDown | KeyCode.ShiftMask, Command.PageDownExtend);
 
-			AddKeyBinding (Key.PageUp, Command.PageUp);
-			AddKeyBinding (((int)'V' + Key.AltMask), Command.PageUp);
+			KeyBindings.Add (KeyCode.PageUp, Command.PageUp);
+			KeyBindings.Add (((int)'V' + KeyCode.AltMask), Command.PageUp);
 
-			AddKeyBinding (Key.PageUp | Key.ShiftMask, Command.PageUpExtend);
+			KeyBindings.Add (KeyCode.PageUp | KeyCode.ShiftMask, Command.PageUpExtend);
 
-			AddKeyBinding (Key.N | Key.CtrlMask, Command.LineDown);
-			AddKeyBinding (Key.CursorDown, Command.LineDown);
+			KeyBindings.Add (KeyCode.N | KeyCode.CtrlMask, Command.LineDown);
+			KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
 
-			AddKeyBinding (Key.CursorDown | Key.ShiftMask, Command.LineDownExtend);
+			KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask, Command.LineDownExtend);
 
-			AddKeyBinding (Key.P | Key.CtrlMask, Command.LineUp);
-			AddKeyBinding (Key.CursorUp, Command.LineUp);
+			KeyBindings.Add (KeyCode.P | KeyCode.CtrlMask, Command.LineUp);
+			KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
 
-			AddKeyBinding (Key.CursorUp | Key.ShiftMask, Command.LineUpExtend);
+			KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask, Command.LineUpExtend);
 
-			AddKeyBinding (Key.F | Key.CtrlMask, Command.Right);
-			AddKeyBinding (Key.CursorRight, Command.Right);
+			KeyBindings.Add (KeyCode.F | KeyCode.CtrlMask, Command.Right);
+			KeyBindings.Add (KeyCode.CursorRight, Command.Right);
 
-			AddKeyBinding (Key.CursorRight | Key.ShiftMask, Command.RightExtend);
+			KeyBindings.Add (KeyCode.CursorRight | KeyCode.ShiftMask, Command.RightExtend);
 
-			AddKeyBinding (Key.B | Key.CtrlMask, Command.Left);
-			AddKeyBinding (Key.CursorLeft, Command.Left);
+			KeyBindings.Add (KeyCode.B | KeyCode.CtrlMask, Command.Left);
+			KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
 
-			AddKeyBinding (Key.CursorLeft | Key.ShiftMask, Command.LeftExtend);
+			KeyBindings.Add (KeyCode.CursorLeft | KeyCode.ShiftMask, Command.LeftExtend);
 
-			AddKeyBinding (Key.Delete, Command.DeleteCharLeft);
-			AddKeyBinding (Key.Backspace, Command.DeleteCharLeft);
+			KeyBindings.Add (KeyCode.Delete, Command.DeleteCharLeft);
+			KeyBindings.Add (KeyCode.Backspace, Command.DeleteCharLeft);
 
-			AddKeyBinding (Key.Home, Command.StartOfLine);
-			AddKeyBinding (Key.A | Key.CtrlMask, Command.StartOfLine);
+			KeyBindings.Add (KeyCode.Home, Command.StartOfLine);
+			KeyBindings.Add (KeyCode.A | KeyCode.CtrlMask, Command.StartOfLine);
 
-			AddKeyBinding (Key.Home | Key.ShiftMask, Command.StartOfLineExtend);
+			KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.StartOfLineExtend);
 
-			AddKeyBinding (Key.DeleteChar, Command.DeleteCharRight);
-			AddKeyBinding (Key.D | Key.CtrlMask, Command.DeleteCharRight);
+			KeyBindings.Add (KeyCode.DeleteChar, Command.DeleteCharRight);
+			KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask, Command.DeleteCharRight);
 
-			AddKeyBinding (Key.End, Command.EndOfLine);
-			AddKeyBinding (Key.E | Key.CtrlMask, Command.EndOfLine);
+			KeyBindings.Add (KeyCode.End, Command.EndOfLine);
+			KeyBindings.Add (KeyCode.E | KeyCode.CtrlMask, Command.EndOfLine);
 
-			AddKeyBinding (Key.End | Key.ShiftMask, Command.EndOfLineExtend);
+			KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.EndOfLineExtend);
 
-			AddKeyBinding (Key.K | Key.CtrlMask, Command.CutToEndLine); // kill-to-end
-			AddKeyBinding (Key.DeleteChar | Key.CtrlMask | Key.ShiftMask, Command.CutToEndLine); // kill-to-end
+			KeyBindings.Add (KeyCode.K | KeyCode.CtrlMask, Command.CutToEndLine); // kill-to-end
+			KeyBindings.Add (KeyCode.DeleteChar | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.CutToEndLine); // kill-to-end
 
-			AddKeyBinding (Key.K | Key.AltMask, Command.CutToStartLine); // kill-to-start
-			AddKeyBinding (Key.Backspace | Key.CtrlMask | Key.ShiftMask, Command.CutToStartLine); // kill-to-start
+			KeyBindings.Add (KeyCode.K | KeyCode.AltMask, Command.CutToStartLine); // kill-to-start
+			KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.CutToStartLine); // kill-to-start
 
-			AddKeyBinding (Key.Y | Key.CtrlMask, Command.Paste); // Control-y, yank
-			AddKeyBinding (Key.Space | Key.CtrlMask, Command.ToggleExtend);
+			KeyBindings.Add (KeyCode.Y | KeyCode.CtrlMask, Command.Paste); // Control-y, yank
+			KeyBindings.Add (KeyCode.Space | KeyCode.CtrlMask, Command.ToggleExtend);
 
-			AddKeyBinding (((int)'C' + Key.AltMask), Command.Copy);
-			AddKeyBinding (Key.C | Key.CtrlMask, Command.Copy);
+			KeyBindings.Add (((int)'C' + KeyCode.AltMask), Command.Copy);
+			KeyBindings.Add (KeyCode.C | KeyCode.CtrlMask, Command.Copy);
 
-			AddKeyBinding (((int)'W' + Key.AltMask), Command.Cut);
-			AddKeyBinding (Key.W | Key.CtrlMask, Command.Cut);
-			AddKeyBinding (Key.X | Key.CtrlMask, Command.Cut);
+			KeyBindings.Add (((int)'W' + KeyCode.AltMask), Command.Cut);
+			KeyBindings.Add (KeyCode.W | KeyCode.CtrlMask, Command.Cut);
+			KeyBindings.Add (KeyCode.X | KeyCode.CtrlMask, Command.Cut);
 
-			AddKeyBinding (Key.CursorLeft | Key.CtrlMask, Command.WordLeft);
-			AddKeyBinding ((Key)((int)'B' + Key.AltMask), Command.WordLeft);
+			KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask, Command.WordLeft);
+			KeyBindings.Add ((KeyCode)((int)'B' + KeyCode.AltMask), Command.WordLeft);
 
-			AddKeyBinding (Key.CursorLeft | Key.CtrlMask | Key.ShiftMask, Command.WordLeftExtend);
+			KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.WordLeftExtend);
 
-			AddKeyBinding (Key.CursorRight | Key.CtrlMask, Command.WordRight);
-			AddKeyBinding ((Key)((int)'F' + Key.AltMask), Command.WordRight);
+			KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask, Command.WordRight);
+			KeyBindings.Add ((KeyCode)((int)'F' + KeyCode.AltMask), Command.WordRight);
 
-			AddKeyBinding (Key.CursorRight | Key.CtrlMask | Key.ShiftMask, Command.WordRightExtend);
-			AddKeyBinding (Key.DeleteChar | Key.CtrlMask, Command.KillWordForwards); // kill-word-forwards
-			AddKeyBinding (Key.Backspace | Key.CtrlMask, Command.KillWordBackwards); // kill-word-backwards
+			KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.WordRightExtend);
+			KeyBindings.Add (KeyCode.DeleteChar | KeyCode.CtrlMask, Command.KillWordForwards); // kill-word-forwards
+			KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask, Command.KillWordBackwards); // kill-word-backwards
 
-			AddKeyBinding (Key.Enter, Command.NewLine);
-			AddKeyBinding (Key.End | Key.CtrlMask, Command.BottomEnd);
-			AddKeyBinding (Key.End | Key.CtrlMask | Key.ShiftMask, Command.BottomEndExtend);
-			AddKeyBinding (Key.Home | Key.CtrlMask, Command.TopHome);
-			AddKeyBinding (Key.Home | Key.CtrlMask | Key.ShiftMask, Command.TopHomeExtend);
-			AddKeyBinding (Key.T | Key.CtrlMask, Command.SelectAll);
-			AddKeyBinding (Key.InsertChar, Command.ToggleOverwrite);
-			AddKeyBinding (Key.Tab, Command.Tab);
-			AddKeyBinding (Key.BackTab | Key.ShiftMask, Command.BackTab);
+			// BUGBUG: If AllowsReturn is false, Key.Enter should not be bound (so that Toplevel can cause Command.Accept).
+			KeyBindings.Add (KeyCode.Enter, Command.NewLine);
+			KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask, Command.BottomEnd);
+			KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.BottomEndExtend);
+			KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask, Command.TopHome);
+			KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.TopHomeExtend);
+			KeyBindings.Add (KeyCode.T | KeyCode.CtrlMask, Command.SelectAll);
+			KeyBindings.Add (KeyCode.InsertChar, Command.ToggleOverwrite);
+			KeyBindings.Add (KeyCode.Tab, Command.Tab);
+			KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask, Command.BackTab);
 
-			AddKeyBinding (Key.Tab | Key.CtrlMask, Command.NextView);
-			AddKeyBinding (Application.AlternateForwardKey, Command.NextView);
+			KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask, Command.NextView);
+			KeyBindings.Add ((KeyCode)Application.AlternateForwardKey, Command.NextView);
 
-			AddKeyBinding (Key.Tab | Key.CtrlMask | Key.ShiftMask, Command.PreviousView);
-			AddKeyBinding (Application.AlternateBackwardKey, Command.PreviousView);
+			KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.PreviousView);
+			KeyBindings.Add ((KeyCode)Application.AlternateBackwardKey, Command.PreviousView);
 
-			AddKeyBinding (Key.Z | Key.CtrlMask, Command.Undo);
-			AddKeyBinding (Key.R | Key.CtrlMask, Command.Redo);
+			KeyBindings.Add (KeyCode.Z | KeyCode.CtrlMask, Command.Undo);
+			KeyBindings.Add (KeyCode.R | KeyCode.CtrlMask, Command.Redo);
 
-			AddKeyBinding (Key.G | Key.CtrlMask, Command.DeleteAll);
-			AddKeyBinding (Key.D | Key.CtrlMask | Key.ShiftMask, Command.DeleteAll);
+			KeyBindings.Add (KeyCode.G | KeyCode.CtrlMask, Command.DeleteAll);
+			KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.DeleteAll);
 
 			_currentCulture = Thread.CurrentThread.CurrentUICulture;
 
 			ContextMenu = new ContextMenu () { MenuItems = BuildContextMenuBarItem () };
 			ContextMenu.KeyChanged += ContextMenu_KeyChanged!;
 
-			AddKeyBinding (ContextMenu.Key, Command.Accept);
+			KeyBindings.Add ((KeyCode)ContextMenu.Key, KeyBindingScope.HotKey, Command.Accept);
 		}
 
 		private MenuBarItem BuildContextMenuBarItem ()
 		{
 			return new MenuBarItem (new MenuItem [] {
-				new MenuItem (Strings.ctxSelectAll, "", () => SelectAll (), null, null, GetKeyFromCommand (Command.SelectAll)),
-				new MenuItem (Strings.ctxDeleteAll, "", () => DeleteAll (), null, null, GetKeyFromCommand (Command.DeleteAll)),
-				new MenuItem (Strings.ctxCopy, "", () => Copy (), null, null, GetKeyFromCommand (Command.Copy)),
-				new MenuItem (Strings.ctxCut, "", () => Cut (), null, null, GetKeyFromCommand (Command.Cut)),
-				new MenuItem (Strings.ctxPaste, "", () => Paste (), null, null, GetKeyFromCommand (Command.Paste)),
-				new MenuItem (Strings.ctxUndo, "", () => Undo (), null, null, GetKeyFromCommand (Command.Undo)),
-				new MenuItem (Strings.ctxRedo, "", () => Redo (), null, null, GetKeyFromCommand (Command.Redo)),
+				new MenuItem (Strings.ctxSelectAll, "", () => SelectAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.SelectAll)),
+				new MenuItem (Strings.ctxDeleteAll, "", () => DeleteAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.DeleteAll)),
+				new MenuItem (Strings.ctxCopy, "", () => Copy (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Copy)),
+				new MenuItem (Strings.ctxCut, "", () => Cut (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Cut)),
+				new MenuItem (Strings.ctxPaste, "", () => Paste (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Paste)),
+				new MenuItem (Strings.ctxUndo, "", () => Undo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Undo)),
+				new MenuItem (Strings.ctxRedo, "", () => Redo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Redo)),
 			});
 		}
 
 		private void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e)
 		{
-			ReplaceKeyBinding (e.OldKey, e.NewKey);
+			KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey);
 		}
 
 		private void Model_LinesLoaded (object sender, EventArgs e)
@@ -1866,12 +1867,12 @@ namespace Terminal.Gui {
 
 		void Top_AlternateBackwardKeyChanged (object sender, KeyChangedEventArgs e)
 		{
-			ReplaceKeyBinding (e.OldKey, e.NewKey);
+			KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey);
 		}
 
 		void Top_AlternateForwardKeyChanged (object sender, KeyChangedEventArgs e)
 		{
-			ReplaceKeyBinding (e.OldKey, e.NewKey);
+			KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey);
 		}
 
 		/// <summary>
@@ -2516,9 +2517,9 @@ namespace Terminal.Gui {
 		{
 			// BUGBUG: (v2 truecolor) This code depends on 8-bit color names; disabling for now
 			//if ((colorScheme!.HotNormal.Foreground & colorScheme.Focus.Background) == colorScheme.Focus.Foreground) {
-				Driver.SetAttribute (new Attribute (colorScheme.Focus.Background, colorScheme.Focus.Foreground));
+			Driver.SetAttribute (new Attribute (colorScheme.Focus.Background, colorScheme.Focus.Foreground));
 			//} else {
-				//Driver.SetAttribute (new Attribute (colorScheme!.HotNormal.Foreground & colorScheme.Focus.Background, colorScheme.Focus.Foreground));
+			//Driver.SetAttribute (new Attribute (colorScheme!.HotNormal.Foreground & colorScheme.Focus.Background, colorScheme.Focus.Foreground));
 			//}
 		}
 
@@ -2882,8 +2883,8 @@ namespace Terminal.Gui {
 				_selectionStartColumn = nStartCol;
 				_wrapNeeded = true;
 
-                SetNeedsDisplay();
-            }
+				SetNeedsDisplay ();
+			}
 			if (_currentCaller != null)
 				throw new InvalidOperationException ($"WordWrap settings was changed after the {_currentCaller} call.");
 		}
@@ -3058,16 +3059,16 @@ namespace Terminal.Gui {
 		{
 			foreach (var ch in toAdd) {
 
-				Key key;
+				KeyCode key;
 
 				try {
-					key = (Key)ch;
+					key = (KeyCode)ch;
 				} catch (Exception) {
 
 					throw new ArgumentException ($"Cannot insert character '{ch}' because it does not map to a Key");
 				}
 
-				InsertText (new KeyEvent () { Key = key });
+				InsertText (new Key () { KeyCode = key });
 			}
 
 			if (NeedsDisplay) {
@@ -3403,28 +3404,32 @@ namespace Terminal.Gui {
 		bool _shiftSelecting;
 
 		///<inheritdoc/>
-		public override bool ProcessKey (KeyEvent kb)
+		public override bool? OnInvokingKeyBindings (Key a)
 		{
-			if (!CanFocus) {
+			// Give autocomplete first opportunity to respond to key presses
+			if (SelectedLength == 0 && Autocomplete.Suggestions.Count > 0 && Autocomplete.ProcessKey (a)) {
 				return true;
 			}
+			return base.OnInvokingKeyBindings (a);
+		}
 
-			// Give autocomplete first opportunity to respond to key presses
-			if (SelectedLength == 0 && Autocomplete.Suggestions.Count > 0 && Autocomplete.ProcessKey (kb)) {
+		///<inheritdoc/>
+		public override bool OnProcessKeyDown (Key a)
+		{
+			if (!CanFocus) {
 				return true;
 			}
 
-			var result = InvokeKeybindings (new KeyEvent (ShortcutHelper.GetModifiersKey (kb),
-			    new KeyModifiers () { Alt = kb.IsAlt, Ctrl = kb.IsCtrl, Shift = kb.IsShift }));
-			if (result != null)
-				return (bool)result;
+
 
 			ResetColumnTrack ();
+
 			// Ignore control characters and other special keys
-			if (kb.Key < Key.Space || kb.Key > Key.CharMask)
+			if (!a.IsKeyCodeAtoZ && (a.KeyCode < KeyCode.Space || a.KeyCode > KeyCode.CharMask)) {
 				return false;
+			}
 
-			InsertText (kb);
+			InsertText (a);
 			DoNeededAction ();
 
 			return true;
@@ -3793,7 +3798,7 @@ namespace Terminal.Gui {
 			if (!AllowsTab || _isReadOnly) {
 				return ProcessMoveNextView ();
 			}
-			InsertText (new KeyEvent ((Key)'\t', null));
+			InsertText (new Key ((KeyCode)'\t'));
 			DoNeededAction ();
 			return true;
 		}
@@ -4320,11 +4325,12 @@ namespace Terminal.Gui {
 			_continuousFind = false;
 		}
 
-		bool InsertText (KeyEvent kb, ColorScheme? colorScheme = null)
+		bool InsertText (Key a, ColorScheme? colorScheme = null)
 		{
 			//So that special keys like tab can be processed
-			if (_isReadOnly)
+			if (_isReadOnly) {
 				return true;
+			}
 
 			SetWrapModel ();
 
@@ -4333,22 +4339,22 @@ namespace Terminal.Gui {
 			if (_selecting) {
 				ClearSelectedRegion ();
 			}
-			if (kb.Key == Key.Enter) {
+			if (a.KeyCode == KeyCode.Enter) {
 				_model.AddLine (_currentRow + 1, new List<RuneCell> ());
 				_currentRow++;
 				_currentColumn = 0;
-			} else if ((uint)kb.Key == 13) {
+			} else if ((uint)a.KeyCode == '\r') {
 				_currentColumn = 0;
 			} else {
 				if (Used) {
-					Insert (new RuneCell { Rune = (Rune)(uint)kb.Key, ColorScheme = colorScheme });
+					Insert (new RuneCell { Rune = a.AsRune, ColorScheme = colorScheme });
 					_currentColumn++;
 					if (_currentColumn >= _leftColumn + Frame.Width) {
 						_leftColumn++;
 						SetNeedsDisplay ();
 					}
 				} else {
-					Insert (new RuneCell { Rune = (Rune)(uint)kb.Key, ColorScheme = colorScheme });
+					Insert (new RuneCell { Rune = a.AsRune, ColorScheme = colorScheme });
 					_currentColumn++;
 				}
 			}
@@ -4390,14 +4396,14 @@ namespace Terminal.Gui {
 		}
 
 		///<inheritdoc/>
-		public override bool OnKeyUp (KeyEvent kb)
+		public override bool OnKeyUp (Key a)
 		{
-			switch (kb.Key) {
-			case Key.Space | Key.CtrlMask:
+			switch (a.KeyCode) {
+			case KeyCode.Space | KeyCode.CtrlMask:
 				return true;
 			}
 
-			return false;
+			return base.OnKeyUp (a);
 		}
 
 		void DoNeededAction ()

+ 76 - 87
Terminal.Gui/Views/TileView.cs

@@ -11,29 +11,30 @@ namespace Terminal.Gui {
 	/// </summary>
 	public class TileView : View {
 		TileView parentTileView;
-
+		
+		// TODO: Update to use Key instead of KeyCode
 		/// <summary>
 		/// The keyboard key that the user can press to toggle resizing
 		/// of splitter lines.  Mouse drag splitting is always enabled.
 		/// </summary>
-		public Key ToggleResizable { get; set; } = Key.CtrlMask | Key.F10;
+		public KeyCode ToggleResizable { get; set; } = KeyCode.CtrlMask | KeyCode.F10;
 
-		List<Tile> tiles;
-		private List<Pos> splitterDistances;
-		private List<TileViewLineView> splitterLines;
+		List<Tile> _tiles;
+		private List<Pos> _splitterDistances;
+		private List<TileViewLineView> _splitterLines;
 
 		/// <summary>
 		/// The sub sections hosted by the view
 		/// </summary>
-		public IReadOnlyCollection<Tile> Tiles => tiles.AsReadOnly ();
+		public IReadOnlyCollection<Tile> Tiles => _tiles.AsReadOnly ();
 
 		/// <summary>
 		/// The splitter locations. Note that there will be N-1 splitters where
 		/// N is the number of <see cref="Tiles"/>.
 		/// </summary>
-		public IReadOnlyCollection<Pos> SplitterDistances => splitterDistances.AsReadOnly ();
+		public IReadOnlyCollection<Pos> SplitterDistances => _splitterDistances.AsReadOnly ();
 
-		private Orientation orientation = Orientation.Vertical;
+		private Orientation _orientation = Orientation.Vertical;
 
 		/// <summary>
 		/// Creates a new instance of the <see cref="TileView"/> class with 
@@ -63,7 +64,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		protected virtual void OnSplitterMoved (int idx)
 		{
-			SplitterMoved?.Invoke (this, new SplitterEventArgs (this, idx, splitterDistances [idx]));
+			SplitterMoved?.Invoke (this, new SplitterEventArgs (this, idx, _splitterDistances [idx]));
 		}
 
 		/// <summary>
@@ -73,22 +74,22 @@ namespace Terminal.Gui {
 		/// <param name="count"></param>
 		public void RebuildForTileCount (int count)
 		{
-			tiles = new List<Tile> ();
-			splitterDistances = new List<Pos> ();
-			if (splitterLines != null) {
-				foreach (var sl in splitterLines) {
+			_tiles = new List<Tile> ();
+			_splitterDistances = new List<Pos> ();
+			if (_splitterLines != null) {
+				foreach (var sl in _splitterLines) {
 					sl.Dispose ();
 				}
 			}
-			splitterLines = new List<TileViewLineView> ();
+			_splitterLines = new List<TileViewLineView> ();
 
 			RemoveAll ();
-			foreach (var tile in tiles) {
+			foreach (var tile in _tiles) {
 				tile.ContentView.Dispose ();
 				tile.ContentView = null;
 			}
-			tiles.Clear ();
-			splitterDistances.Clear ();
+			_tiles.Clear ();
+			_splitterDistances.Clear ();
 
 			if (count == 0) {
 				return;
@@ -97,14 +98,14 @@ namespace Terminal.Gui {
 			for (int i = 0; i < count; i++) {
 				if (i > 0) {
 					var currentPos = Pos.Percent ((100 / count) * i);
-					splitterDistances.Add (currentPos);
+					_splitterDistances.Add (currentPos);
 					var line = new TileViewLineView (this, i - 1);
 					Add (line);
-					splitterLines.Add (line);
+					_splitterLines.Add (line);
 				}
 
 				var tile = new Tile ();
-				tiles.Add (tile);
+				_tiles.Add (tile);
 				Add (tile.ContentView);
 				tile.TitleChanged += (s, e) => SetNeedsDisplay ();
 			}
@@ -126,21 +127,21 @@ namespace Terminal.Gui {
 
 			Tile toReturn = null;
 
-			for (int i = 0; i < tiles.Count; i++) {
+			for (int i = 0; i < _tiles.Count; i++) {
 
 				if (i != idx) {
 					var oldTile = oldTiles [i > idx ? i - 1 : i];
 
 					// remove the new empty View
-					Remove (tiles [i].ContentView);
-					tiles [i].ContentView.Dispose ();
-					tiles [i].ContentView = null;
+					Remove (_tiles [i].ContentView);
+					_tiles [i].ContentView.Dispose ();
+					_tiles [i].ContentView = null;
 
 					// restore old Tile and View
-					tiles [i] = oldTile;
-					Add (tiles [i].ContentView);
+					_tiles [i] = oldTile;
+					Add (_tiles [i].ContentView);
 				} else {
-					toReturn = tiles [i];
+					toReturn = _tiles [i];
 				}
 			}
 			SetNeedsDisplay ();
@@ -169,19 +170,19 @@ namespace Terminal.Gui {
 
 			RebuildForTileCount (oldTiles.Length - 1);
 
-			for (int i = 0; i < tiles.Count; i++) {
+			for (int i = 0; i < _tiles.Count; i++) {
 
 				int oldIdx = i >= idx ? i + 1 : i;
 				var oldTile = oldTiles [oldIdx];
 
 				// remove the new empty View
-				Remove (tiles [i].ContentView);
-				tiles [i].ContentView.Dispose ();
-				tiles [i].ContentView = null;
+				Remove (_tiles [i].ContentView);
+				_tiles [i].ContentView.Dispose ();
+				_tiles [i].ContentView = null;
 
 				// restore old Tile and View
-				tiles [i] = oldTile;
-				Add (tiles [i].ContentView);
+				_tiles [i] = oldTile;
+				Add (_tiles [i].ContentView);
 
 			}
 			SetNeedsDisplay ();
@@ -196,8 +197,8 @@ namespace Terminal.Gui {
 		///</summary>
 		public int IndexOf (View toFind, bool recursive = false)
 		{
-			for (int i = 0; i < tiles.Count; i++) {
-				var v = tiles [i].ContentView;
+			for (int i = 0; i < _tiles.Count; i++) {
+				var v = _tiles [i].ContentView;
 
 				if (v == toFind) {
 					return i;
@@ -236,9 +237,9 @@ namespace Terminal.Gui {
 		/// Orientation of the dividing line (Horizontal or Vertical).
 		/// </summary>
 		public Orientation Orientation {
-			get { return orientation; }
+			get { return _orientation; }
 			set {
-				orientation = value;
+				_orientation = value;
 				if (IsInitialized) {
 					LayoutSubviews ();
 				}
@@ -266,7 +267,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// <para>Attempts to update the <see cref="splitterDistances"/> of line at <paramref name="idx"/>
+		/// <para>Attempts to update the <see cref="SplitterDistances"/> of line at <paramref name="idx"/>
 		/// to the new <paramref name="value"/>. Returns false if the new position is not allowed because of
 		/// <see cref="Tile.MinSize"/>, location of other splitters etc.
 		/// </para>
@@ -279,13 +280,13 @@ namespace Terminal.Gui {
 				throw new ArgumentException ($"Only Percent and Absolute values are supported. Passed value was {value.GetType ().Name}");
 			}
 
-			var fullSpace = orientation == Orientation.Vertical ? Bounds.Width : Bounds.Height;
+			var fullSpace = _orientation == Orientation.Vertical ? Bounds.Width : Bounds.Height;
 
 			if (fullSpace != 0 && !IsValidNewSplitterPos (idx, value, fullSpace)) {
 				return false;
 			}
 
-			splitterDistances [idx] = value;
+			_splitterDistances [idx] = value;
 			GetRootTileView ().LayoutSubviews ();
 			OnSplitterMoved (idx);
 			return true;
@@ -329,7 +330,7 @@ namespace Terminal.Gui {
 				}
 
 				foreach (var line in allLines) {
-					bool isRoot = splitterLines.Contains (line);
+					bool isRoot = _splitterLines.Contains (line);
 
 					line.BoundsToScreen (0, 0, out var x1, out var y1);
 					var origin = ScreenToFrame (x1, y1);
@@ -399,7 +400,7 @@ namespace Terminal.Gui {
 		{
 			// when splitting a view into 2 sub views we will need to migrate
 			// the title too
-			var tile = tiles [idx];
+			var tile = _tiles [idx];
 
 			var title = tile.Title;
 			View toMove = tile.ContentView;
@@ -428,27 +429,28 @@ namespace Terminal.Gui {
 
 			tile.ContentView = newContainer;
 
-			var newTileView1 = newContainer.tiles [0].ContentView;
+			var newTileView1 = newContainer._tiles [0].ContentView;
 			// Add the original content into the first view of the new container
 			foreach (var childView in childViews) {
 				newTileView1.Add (childView);
 			}
 
 			// Move the title across too
-			newContainer.tiles [0].Title = title;
+			newContainer._tiles [0].Title = title;
 			tile.Title = string.Empty;
 
 			result = newContainer;
 			return true;
 		}
 
+		//// BUGBUG: Why is this not handled by a key binding???
 		/// <inheritdoc/>
-		public override bool ProcessHotKey (KeyEvent keyEvent)
+		public override bool OnProcessKeyDown (Key keyEvent)
 		{
 			bool focusMoved = false;
 
-			if (keyEvent.Key == ToggleResizable) {
-				foreach (var l in splitterLines) {
+			if (keyEvent.KeyCode == ToggleResizable) {
+				foreach (var l in _splitterLines) {
 
 					var iniBefore = l.IsInitialized;
 					l.IsInitialized = false;
@@ -463,13 +465,13 @@ namespace Terminal.Gui {
 				return true;
 			}
 
-			return base.ProcessHotKey (keyEvent);
+			return false;
 		}
 
 		private bool IsValidNewSplitterPos (int idx, Pos value, int fullSpace)
 		{
 			int newSize = value.Anchor (fullSpace);
-			bool isGettingBigger = newSize > splitterDistances [idx].Anchor (fullSpace);
+			bool isGettingBigger = newSize > _splitterDistances [idx].Anchor (fullSpace);
 			int lastSplitterOrBorder = HasBorder () ? 1 : 0;
 			int nextSplitterOrBorder = HasBorder () ? fullSpace - 1 : fullSpace;
 
@@ -491,7 +493,7 @@ namespace Terminal.Gui {
 
 			// Do not allow splitter to move left of the one before
 			if (idx > 0) {
-				int posLeft = splitterDistances [idx - 1].Anchor (fullSpace);
+				int posLeft = _splitterDistances [idx - 1].Anchor (fullSpace);
 
 				if (newSize <= posLeft) {
 					return false;
@@ -501,8 +503,8 @@ namespace Terminal.Gui {
 			}
 
 			// Do not allow splitter to move right of the one after
-			if (idx + 1 < splitterDistances.Count) {
-				int posRight = splitterDistances [idx + 1].Anchor (fullSpace);
+			if (idx + 1 < _splitterDistances.Count) {
+				int posRight = _splitterDistances [idx + 1].Anchor (fullSpace);
 
 				if (newSize >= posRight) {
 					return false;
@@ -519,7 +521,7 @@ namespace Terminal.Gui {
 				}
 
 				// don't grow if it would take us below min size of right panel
-				if (spaceForNext < tiles [idx + 1].MinSize) {
+				if (spaceForNext < _tiles [idx + 1].MinSize) {
 					return false;
 				}
 			} else {
@@ -531,7 +533,7 @@ namespace Terminal.Gui {
 				}
 
 				// don't shrink if it would take us below min size of left panel
-				if (spaceForLast < tiles [idx].MinSize) {
+				if (spaceForLast < _tiles [idx].MinSize) {
 					return false;
 				}
 			}
@@ -629,22 +631,22 @@ namespace Terminal.Gui {
 				return;
 			}
 
-			for (int i = 0; i < splitterLines.Count; i++) {
-				var line = splitterLines [i];
+			for (int i = 0; i < _splitterLines.Count; i++) {
+				var line = _splitterLines [i];
 
 				line.Orientation = Orientation;
-				line.Width = orientation == Orientation.Vertical
+				line.Width = _orientation == Orientation.Vertical
 					? 1 : Dim.Fill ();
-				line.Height = orientation == Orientation.Vertical
+				line.Height = _orientation == Orientation.Vertical
 					? Dim.Fill () : 1;
-				line.LineRune = orientation == Orientation.Vertical ?
+				line.LineRune = _orientation == Orientation.Vertical ?
 					CM.Glyphs.VLine : CM.Glyphs.HLine;
 
-				if (orientation == Orientation.Vertical) {
-					line.X = splitterDistances [i];
+				if (_orientation == Orientation.Vertical) {
+					line.X = _splitterDistances [i];
 					line.Y = 0;
 				} else {
-					line.Y = splitterDistances [i];
+					line.Y = _splitterDistances [i];
 					line.X = 0;
 				}
 
@@ -652,8 +654,8 @@ namespace Terminal.Gui {
 
 			HideSplittersBasedOnTileVisibility ();
 
-			var visibleTiles = tiles.Where (t => t.ContentView.Visible).ToArray ();
-			var visibleSplitterLines = splitterLines.Where (l => l.Visible).ToArray ();
+			var visibleTiles = _tiles.Where (t => t.ContentView.Visible).ToArray ();
+			var visibleSplitterLines = _splitterLines.Where (l => l.Visible).ToArray ();
 
 			for (int i = 0; i < visibleTiles.Length; i++) {
 				var tile = visibleTiles [i];
@@ -674,20 +676,20 @@ namespace Terminal.Gui {
 
 		private void HideSplittersBasedOnTileVisibility ()
 		{
-			if (splitterLines.Count == 0) {
+			if (_splitterLines.Count == 0) {
 				return;
 			}
 
-			foreach (var line in splitterLines) {
+			foreach (var line in _splitterLines) {
 				line.Visible = true;
 			}
 
-			for (int i = 0; i < tiles.Count; i++) {
-				if (!tiles [i].ContentView.Visible) {
+			for (int i = 0; i < _tiles.Count; i++) {
+				if (!_tiles [i].ContentView.Visible) {
 
 					// when a tile is not visible, prefer hiding
 					// the splitter on it's left
-					var candidate = splitterLines [Math.Max (0, i - 1)];
+					var candidate = _splitterLines [Math.Max (0, i - 1)];
 
 					// unless that splitter is already hidden
 					// e.g. when hiding panels 0 and 1 of a 3 panel 
@@ -695,7 +697,7 @@ namespace Terminal.Gui {
 					if (candidate.Visible) {
 						candidate.Visible = false;
 					} else {
-						splitterLines [Math.Min (i, splitterLines.Count - 1)].Visible = false;
+						_splitterLines [Math.Min (i, _splitterLines.Count - 1)].Visible = false;
 					}
 
 				}
@@ -799,23 +801,10 @@ namespace Terminal.Gui {
 					return MoveSplitter (0, 1);
 				});
 
-				AddKeyBinding (Key.CursorRight, Command.Right);
-				AddKeyBinding (Key.CursorLeft, Command.Left);
-				AddKeyBinding (Key.CursorUp, Command.LineUp);
-				AddKeyBinding (Key.CursorDown, Command.LineDown);
-			}
-
-			public override bool ProcessKey (KeyEvent kb)
-			{
-				if (!CanFocus || !HasFocus) {
-					return base.ProcessKey (kb);
-				}
-
-				var result = InvokeKeybindings (kb);
-				if (result != null)
-					return (bool)result;
-
-				return base.ProcessKey (kb);
+				KeyBindings.Add (KeyCode.CursorRight, Command.Right);
+				KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
+				KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
+				KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
 			}
 
 			public override void PositionCursor ()

+ 26 - 26
Terminal.Gui/Views/TimeField.cs

@@ -80,30 +80,30 @@ namespace Terminal.Gui {
 
 			// Things this view knows how to do
 			AddCommand (Command.DeleteCharRight, () => { DeleteCharRight (); return true; });
-			AddCommand (Command.DeleteCharLeft, () => { DeleteCharLeft (); return true; });
+			AddCommand (Command.DeleteCharLeft, () => { DeleteCharLeft (false); return true; });
 			AddCommand (Command.LeftHome, () => MoveHome ());
 			AddCommand (Command.Left, () => MoveLeft ());
 			AddCommand (Command.RightEnd, () => MoveEnd ());
 			AddCommand (Command.Right, () => MoveRight ());
 
 			// Default keybindings for this view
-			AddKeyBinding (Key.DeleteChar, Command.DeleteCharRight);
-			AddKeyBinding (Key.D | Key.CtrlMask, Command.DeleteCharRight);
+			KeyBindings.Add (KeyCode.DeleteChar, Command.DeleteCharRight);
+			KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask, Command.DeleteCharRight);
 
-			AddKeyBinding (Key.Delete, Command.DeleteCharLeft);
-			AddKeyBinding (Key.Backspace, Command.DeleteCharLeft);
+			KeyBindings.Add (KeyCode.Delete, Command.DeleteCharLeft);
+			KeyBindings.Add (KeyCode.Backspace, Command.DeleteCharLeft);
 
-			AddKeyBinding (Key.Home, Command.LeftHome);
-			AddKeyBinding (Key.A | Key.CtrlMask, Command.LeftHome);
+			KeyBindings.Add (KeyCode.Home, Command.LeftHome);
+			KeyBindings.Add (KeyCode.A | KeyCode.CtrlMask, Command.LeftHome);
 
-			AddKeyBinding (Key.CursorLeft, Command.Left);
-			AddKeyBinding (Key.B | Key.CtrlMask, Command.Left);
+			KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
+			KeyBindings.Add (KeyCode.B | KeyCode.CtrlMask, Command.Left);
 
-			AddKeyBinding (Key.End, Command.RightEnd);
-			AddKeyBinding (Key.E | Key.CtrlMask, Command.RightEnd);
+			KeyBindings.Add (KeyCode.End, Command.RightEnd);
+			KeyBindings.Add (KeyCode.E | KeyCode.CtrlMask, Command.RightEnd);
 
-			AddKeyBinding (Key.CursorRight, Command.Right);
-			AddKeyBinding (Key.F | Key.CtrlMask, Command.Right);
+			KeyBindings.Add (KeyCode.CursorRight, Command.Right);
+			KeyBindings.Add (KeyCode.F | KeyCode.CtrlMask, Command.Right);
 		}
 
 		void TextField_TextChanged (object sender, TextChangedEventArgs e)
@@ -247,23 +247,23 @@ namespace Terminal.Gui {
 		}
 
 		///<inheritdoc/>
-		public override bool ProcessKey (KeyEvent kb)
+		public override bool OnProcessKeyDown (Key a)
 		{
-			var result = InvokeKeybindings (kb);
-			if (result != null)
-				return (bool)result;
-
 			// Ignore non-numeric characters.
-			if (kb.Key < (Key)((int)Key.D0) || kb.Key > (Key)((int)Key.D9))
-				return false;
-
-			if (ReadOnly)
+			if (a.KeyCode is >= (KeyCode)(int)KeyCode.D0 and <= (KeyCode)(int)KeyCode.D9) {
+				if (!ReadOnly) {
+					if (SetText ((Rune)a)) {
+						IncCursorPosition ();
+					}
+				}
 				return true;
+			}
 
-			if (SetText (((Rune)(uint)kb.Key).ToString ().EnumerateRunes ().First ()))
-				IncCursorPosition ();
-
-			return true;
+			if (a.IsKeyCodeAtoZ) {
+				return true;
+			}
+			
+			return false;
 		}
 
 		bool MoveRight ()

+ 42 - 103
Terminal.Gui/Views/Toplevel.cs

@@ -23,7 +23,7 @@ namespace Terminal.Gui {
 	/// </remarks>
 	public partial class Toplevel : View {
 		/// <summary>
-		/// Gets or sets whether the <see cref="MainLoop"/> for this <see cref="Toplevel"/> is running or not. 
+		/// Gets or sets whether the main loop for this <see cref="Toplevel"/> is running or not. 
 		/// </summary>
 		/// <remarks>
 		///    Setting this property directly is discouraged. Use <see cref="Application.RequestStop"/> instead. 
@@ -38,7 +38,7 @@ namespace Terminal.Gui {
 		public event EventHandler Loaded;
 
 		/// <summary>
-		/// Invoked when the <see cref="Toplevel"/> <see cref="MainLoop"/> has started it's first iteration.
+		/// Invoked when the <see cref="Toplevel"/> main loop has started it's first iteration.
 		/// Subscribe to this event to perform tasks when the <see cref="Toplevel"/> has been laid out and focus has been set.
 		/// changes. 
 		/// <para>A Ready event handler is a good place to finalize initialization after calling 
@@ -138,7 +138,7 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Called from <see cref="Application.Begin(Toplevel)"/> before the <see cref="Toplevel"/> redraws for the first time. 
 		/// </summary>
-		virtual public void OnLoaded ()
+		public virtual void OnLoaded ()
 		{
 			IsLoaded = true;
 			foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) {
@@ -209,31 +209,42 @@ namespace Terminal.Gui {
 			AddCommand (Command.NextViewOrTop, () => { MoveNextViewOrTop (); return true; });
 			AddCommand (Command.PreviousViewOrTop, () => { MovePreviousViewOrTop (); return true; });
 			AddCommand (Command.Refresh, () => { Application.Refresh (); return true; });
+			AddCommand (Command.Accept, () => {
+				// TODO: Perhaps all views should support the concept of being default?
+				// TODO: It's bad that Toplevel is tightly coupled with Button
+				if (Subviews.FirstOrDefault(v => v is Button && ((Button)v).IsDefault && ((Button)v).Enabled) is Button defaultBtn) {
+					defaultBtn.InvokeCommand (Command.Accept);
+					return true;
+				}
+				return false;
+			});
 
 			// Default keybindings for this view
-			AddKeyBinding (Application.QuitKey, Command.QuitToplevel);
-			AddKeyBinding (Key.Z | Key.CtrlMask, Command.Suspend);
-
-			AddKeyBinding (Key.Tab, Command.NextView);
-
-			AddKeyBinding (Key.CursorRight, Command.NextView);
-			AddKeyBinding (Key.F | Key.CtrlMask, Command.NextView);
-
-			AddKeyBinding (Key.CursorDown, Command.NextView);
-			AddKeyBinding (Key.I | Key.CtrlMask, Command.NextView); // Unix
-
-			AddKeyBinding (Key.BackTab | Key.ShiftMask, Command.PreviousView);
-			AddKeyBinding (Key.CursorLeft, Command.PreviousView);
-			AddKeyBinding (Key.CursorUp, Command.PreviousView);
-			AddKeyBinding (Key.B | Key.CtrlMask, Command.PreviousView);
-
-			AddKeyBinding (Key.Tab | Key.CtrlMask, Command.NextViewOrTop);
-			AddKeyBinding (Application.AlternateForwardKey, Command.NextViewOrTop); // Needed on Unix
-
-			AddKeyBinding (Key.Tab | Key.ShiftMask | Key.CtrlMask, Command.PreviousViewOrTop);
-			AddKeyBinding (Application.AlternateBackwardKey, Command.PreviousViewOrTop); // Needed on Unix
-
-			AddKeyBinding (Key.L | Key.CtrlMask, Command.Refresh);
+			KeyBindings.Add ((KeyCode)Application.QuitKey, Command.QuitToplevel);
+
+			KeyBindings.Add (KeyCode.CursorRight, Command.NextView);
+			KeyBindings.Add (KeyCode.CursorDown, Command.NextView);
+			KeyBindings.Add (KeyCode.CursorLeft, Command.PreviousView);
+			KeyBindings.Add (KeyCode.CursorUp, Command.PreviousView);
+
+			KeyBindings.Add (KeyCode.Tab, Command.NextView);
+			KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask, Command.PreviousView);
+			KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask, Command.NextViewOrTop);
+			KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.PreviousViewOrTop);
+
+			KeyBindings.Add (KeyCode.F5, Command.Refresh);
+			KeyBindings.Add ((KeyCode)Application.AlternateForwardKey, Command.NextViewOrTop); // Needed on Unix
+			KeyBindings.Add ((KeyCode)Application.AlternateBackwardKey, Command.PreviousViewOrTop); // Needed on Unix
+
+#if UNIX_KEY_BINDINGS
+			KeyBindings.Add (Key.Z | Key.CtrlMask, Command.Suspend);
+			KeyBindings.Add (Key.L | Key.CtrlMask, Command.Refresh);// Unix
+			KeyBindings.Add (Key.F | Key.CtrlMask, Command.NextView);// Unix
+			KeyBindings.Add (Key.I | Key.CtrlMask, Command.NextView); // Unix
+			KeyBindings.Add (Key.B | Key.CtrlMask, Command.PreviousView);// Unix
+#endif
+			// This enables the default button to be activated by the Enter key.
+			KeyBindings.Add (KeyCode.Enter, Command.Accept);
 		}
 
 		private void Application_UnGrabbingMouse (object sender, GrabMouseEventArgs e)
@@ -261,7 +272,7 @@ namespace Terminal.Gui {
 		/// <param name="e"></param>
 		public virtual void OnAlternateForwardKeyChanged (KeyChangedEventArgs e)
 		{
-			ReplaceKeyBinding (e.OldKey, e.NewKey);
+			KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey);
 			AlternateForwardKeyChanged?.Invoke (this, e);
 		}
 
@@ -276,7 +287,7 @@ namespace Terminal.Gui {
 		/// <param name="e"></param>
 		public virtual void OnAlternateBackwardKeyChanged (KeyChangedEventArgs e)
 		{
-			ReplaceKeyBinding (e.OldKey, e.NewKey);
+			KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey);
 			AlternateBackwardKeyChanged?.Invoke (this, e);
 		}
 
@@ -291,7 +302,7 @@ namespace Terminal.Gui {
 		/// <param name="e"></param>
 		public virtual void OnQuitKeyChanged (KeyChangedEventArgs e)
 		{
-			ReplaceKeyBinding (e.OldKey, e.NewKey);
+			KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey);
 			QuitKeyChanged?.Invoke (this, e);
 		}
 
@@ -318,7 +329,7 @@ namespace Terminal.Gui {
 		/// 
 		/// <list type="bullet">
 		///   <item>
-		///		<description><see cref="ProcessKey(KeyEvent)"/> events will propagate keys upwards.</description>
+		///		<description><see cref="View.OnKeyDown"/> events will propagate keys upwards.</description>
 		///   </item>
 		///   <item>
 		///		<description>The Toplevel will act as an embedded view (not a modal/pop-up).</description>
@@ -329,7 +340,7 @@ namespace Terminal.Gui {
 		/// 
 		/// <list type="bullet">
 		///   <item>
-		///		<description><see cref="ProcessKey(KeyEvent)"/> events will NOT propogate keys upwards.</description>
+		///		<description><see cref="View.OnKeyDown"/> events will NOT propagate keys upwards.</description>
 		///	  </item>
 		///   <item>
 		///		<description>The Toplevel will and look like a modal (pop-up) (e.g. see <see cref="Dialog"/>.</description>
@@ -354,65 +365,6 @@ namespace Terminal.Gui {
 		/// </summary>
 		public bool IsLoaded { get; private set; }
 
-		///<inheritdoc/>
-		public override bool OnKeyDown (KeyEvent keyEvent)
-		{
-			if (base.OnKeyDown (keyEvent)) {
-				return true;
-			}
-
-			switch (keyEvent.Key) {
-			case Key.AltMask:
-			case Key.AltMask | Key.Space:
-			case Key.CtrlMask | Key.Space:
-			case Key _ when (keyEvent.Key & Key.AltMask) == Key.AltMask:
-				return MenuBar != null && MenuBar.OnKeyDown (keyEvent);
-			}
-
-			return false;
-		}
-
-		///<inheritdoc/>
-		public override bool OnKeyUp (KeyEvent keyEvent)
-		{
-			if (base.OnKeyUp (keyEvent)) {
-				return true;
-			}
-
-			switch (keyEvent.Key) {
-			case Key.AltMask:
-			case Key.AltMask | Key.Space:
-			case Key.CtrlMask | Key.Space:
-				if (MenuBar != null && MenuBar.OnKeyUp (keyEvent)) {
-					return true;
-				}
-				break;
-			}
-
-			return false;
-		}
-
-		///<inheritdoc/>
-		public override bool ProcessKey (KeyEvent keyEvent)
-		{
-			if (base.ProcessKey (keyEvent))
-				return true;
-
-			var result = InvokeKeybindings (new KeyEvent (ShortcutHelper.GetModifiersKey (keyEvent),
-				new KeyModifiers () { Alt = keyEvent.IsAlt, Ctrl = keyEvent.IsCtrl, Shift = keyEvent.IsShift }));
-			if (result != null)
-				return (bool)result;
-
-#if false
-			if (keyEvent.Key == Key.F5) {
-				Application.DebugDrawBounds = !Application.DebugDrawBounds;
-				SetNeedsDisplay ();
-				return true;
-			}
-#endif
-			return false;
-		}
-
 		private void MovePreviousViewOrTop ()
 		{
 			if (Application.OverlappedTop == null) {
@@ -478,19 +430,6 @@ namespace Terminal.Gui {
 			}
 		}
 
-		///<inheritdoc/>
-		public override bool ProcessColdKey (KeyEvent keyEvent)
-		{
-			if (base.ProcessColdKey (keyEvent)) {
-				return true;
-			}
-
-			if (ShortcutHelper.FindAndOpenByShortcut (keyEvent, this)) {
-				return true;
-			}
-			return false;
-		}
-
 		View GetDeepestFocusedSubview (View view)
 		{
 			if (view == null) {

+ 2 - 0
Terminal.Gui/Views/TreeView/Branch.cs

@@ -195,6 +195,8 @@ namespace Terminal.Gui {
 				if (modelScheme != null) {
 					// use it
 					modelColor = isSelected ? modelScheme.Focus : modelScheme.Normal;
+				} else {
+					modelColor = new Attribute ();
 				}
 			}
 

+ 32 - 34
Terminal.Gui/Views/TreeView/TreeView.cs

@@ -14,7 +14,7 @@ namespace Terminal.Gui {
 	/// <summary>
 	/// Interface for all non generic members of <see cref="TreeView{T}"/>.
 	/// 
-	/// <a href="https://gui-cs.github.io/Terminal.Gui/docs/treeview.html">See TreeView Deep Dive for more information</a>.
+	/// <a href="../docs/treeview.md">See TreeView Deep Dive for more information</a>.
 	/// </summary>
 	public interface ITreeView {
 		/// <summary>
@@ -37,7 +37,7 @@ namespace Terminal.Gui {
 	/// Convenience implementation of generic <see cref="TreeView{T}"/> for any tree were all nodes
 	/// implement <see cref="ITreeNode"/>.
 	/// 
-	/// <a href="https://gui-cs.github.io/Terminal.Gui/docs/treeview.html">See TreeView Deep Dive for more information</a>.
+	/// <a href="../docs/treeview.md">See TreeView Deep Dive for more information</a>.
 	/// </summary>
 	public class TreeView : TreeView<ITreeNode> {
 
@@ -56,7 +56,7 @@ namespace Terminal.Gui {
 	/// Hierarchical tree view with expandable branches. Branch objects are dynamically determined
 	/// when expanded using a user defined <see cref="ITreeBuilder{T}"/>.
 	/// 
-	/// <a href="https://gui-cs.github.io/Terminal.Gui/docs/treeview.html">See TreeView Deep Dive for more information</a>.
+	/// <a href="../docs/treeview.md">See TreeView Deep Dive for more information</a>.
 	/// </summary>
 	public class TreeView<T> : View, ITreeView where T : class {
 		private int scrollOffsetVertical;
@@ -119,15 +119,16 @@ namespace Terminal.Gui {
 		/// </summary>
 		public event EventHandler<ObjectActivatedEventArgs<T>> ObjectActivated;
 
+		// TODO: Update to use Key instead of KeyCode
 		/// <summary>
 		/// Key which when pressed triggers <see cref="TreeView{T}.ObjectActivated"/>.
 		/// Defaults to Enter.
 		/// </summary>
-		public Key ObjectActivationKey {
+		public KeyCode ObjectActivationKey {
 			get => objectActivationKey;
 			set {
 				if (objectActivationKey != value) {
-					ReplaceKeyBinding (ObjectActivationKey, value);
+					KeyBindings.Replace (ObjectActivationKey, value);
 					objectActivationKey = value;
 				}
 			}
@@ -162,7 +163,7 @@ namespace Terminal.Gui {
 		/// (nodes added but no tree builder set).
 		/// </summary>
 		public static string NoBuilderError = "ERROR: TreeBuilder Not Set";
-		private Key objectActivationKey = Key.Enter;
+		private KeyCode objectActivationKey = KeyCode.Enter;
 
 		/// <summary>
 		/// Called when the <see cref="SelectedObject"/> changes.
@@ -286,27 +287,27 @@ namespace Terminal.Gui {
 			AddCommand (Command.Accept, () => { ActivateSelectedObjectIfAny (); return true; });
 
 			// Default keybindings for this view
-			AddKeyBinding (Key.PageUp, Command.PageUp);
-			AddKeyBinding (Key.PageDown, Command.PageDown);
-			AddKeyBinding (Key.PageUp | Key.ShiftMask, Command.PageUpExtend);
-			AddKeyBinding (Key.PageDown | Key.ShiftMask, Command.PageDownExtend);
-			AddKeyBinding (Key.CursorRight, Command.Expand);
-			AddKeyBinding (Key.CursorRight | Key.CtrlMask, Command.ExpandAll);
-			AddKeyBinding (Key.CursorLeft, Command.Collapse);
-			AddKeyBinding (Key.CursorLeft | Key.CtrlMask, Command.CollapseAll);
+			KeyBindings.Add (KeyCode.PageUp, Command.PageUp);
+			KeyBindings.Add (KeyCode.PageDown, Command.PageDown);
+			KeyBindings.Add (KeyCode.PageUp | KeyCode.ShiftMask, Command.PageUpExtend);
+			KeyBindings.Add (KeyCode.PageDown | KeyCode.ShiftMask, Command.PageDownExtend);
+			KeyBindings.Add (KeyCode.CursorRight, Command.Expand);
+			KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask, Command.ExpandAll);
+			KeyBindings.Add (KeyCode.CursorLeft, Command.Collapse);
+			KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask, Command.CollapseAll);
 
-			AddKeyBinding (Key.CursorUp, Command.LineUp);
-			AddKeyBinding (Key.CursorUp | Key.ShiftMask, Command.LineUpExtend);
-			AddKeyBinding (Key.CursorUp | Key.CtrlMask, Command.LineUpToFirstBranch);
+			KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
+			KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask, Command.LineUpExtend);
+			KeyBindings.Add (KeyCode.CursorUp | KeyCode.CtrlMask, Command.LineUpToFirstBranch);
 
-			AddKeyBinding (Key.CursorDown, Command.LineDown);
-			AddKeyBinding (Key.CursorDown | Key.ShiftMask, Command.LineDownExtend);
-			AddKeyBinding (Key.CursorDown | Key.CtrlMask, Command.LineDownToLastBranch);
+			KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
+			KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask, Command.LineDownExtend);
+			KeyBindings.Add (KeyCode.CursorDown | KeyCode.CtrlMask, Command.LineDownToLastBranch);
 
-			AddKeyBinding (Key.Home, Command.TopHome);
-			AddKeyBinding (Key.End, Command.BottomEnd);
-			AddKeyBinding (Key.A | Key.CtrlMask, Command.SelectAll);
-			AddKeyBinding (ObjectActivationKey, Command.Accept);
+			KeyBindings.Add (KeyCode.Home, Command.TopHome);
+			KeyBindings.Add (KeyCode.End, Command.BottomEnd);
+			KeyBindings.Add (KeyCode.A | KeyCode.CtrlMask, Command.SelectAll);
+			KeyBindings.Add (ObjectActivationKey, Command.Accept);
 		}
 
 		/// <summary>
@@ -621,19 +622,14 @@ namespace Terminal.Gui {
 		public CollectionNavigator KeystrokeNavigator { get; private set; } = new CollectionNavigator ();
 
 		/// <inheritdoc/>
-		public override bool ProcessKey (KeyEvent keyEvent)
+		public override bool OnProcessKeyDown (Key keyEvent)
 		{
 			if (!Enabled) {
 				return false;
 			}
 
 			try {
-				// First of all deal with any registered keybindings
-				var result = InvokeKeybindings (keyEvent);
-				if (result != null) {
-					return (bool)result;
-				}
-
+				// BUGBUG: this should move to OnInvokingKeyBindings
 				// If not a keybinding, is the key a searchable key press?
 				if (CollectionNavigator.IsCompatibleKey (keyEvent) && AllowLetterBasedNavigation) {
 					IReadOnlyCollection<Branch<T>> map;
@@ -644,7 +640,7 @@ namespace Terminal.Gui {
 
 					// Find the current selected object within the tree
 					var current = map.IndexOf (b => b.Model == SelectedObject);
-					var newIndex = KeystrokeNavigator?.GetNextMatchingItem (current, (char)keyEvent.KeyValue);
+					var newIndex = KeystrokeNavigator?.GetNextMatchingItem (current, (char)keyEvent);
 
 					if (newIndex is int && newIndex != -1) {
 						SelectedObject = map.ElementAt ((int)newIndex).Model;
@@ -654,10 +650,12 @@ namespace Terminal.Gui {
 					}
 				}
 			} finally {
-				PositionCursor ();
+				if (IsInitialized) {
+					PositionCursor ();
+				}
 			}
 
-			return base.ProcessKey (keyEvent);
+			return false;
 		}
 
 		/// <summary>

+ 457 - 465
Terminal.Gui/Views/Wizard/Wizard.cs

@@ -4,532 +4,524 @@ using System.Linq;
 using System.Text;
 using Terminal.Gui.Resources;
 
-namespace Terminal.Gui {
-
+namespace Terminal.Gui; 
+
+/// <summary>
+/// Provides navigation and a user interface (UI) to collect related data across multiple steps. Each step (<see cref="WizardStep"/>) can host 
+/// arbitrary <see cref="View"/>s, much like a <see cref="Dialog"/>. Each step also has a pane for help text. Along the
+/// bottom of the Wizard view are customizable buttons enabling the user to navigate forward and backward through the Wizard. 
+/// </summary>
+/// <remarks>
+/// The Wizard can be displayed either as a modal (pop-up) <see cref="Window"/> (like <see cref="Dialog"/>) or as an embedded <see cref="View"/>. 
+/// 
+/// By default, <see cref="Wizard.Modal"/> is <c>true</c>. In this case launch the Wizard with <c>Application.Run(wizard)</c>. 
+/// 
+/// See <see cref="Wizard.Modal"/> for more details.
+/// </remarks>
+/// <example>
+/// <code>
+/// using Terminal.Gui;
+/// using System.Text;
+/// 
+/// Application.Init();
+/// 
+/// var wizard = new Wizard ($"Setup Wizard");
+/// 
+/// // Add 1st step
+/// var firstStep = new WizardStep ("End User License Agreement");
+/// wizard.AddStep(firstStep);
+/// firstStep.NextButtonText = "Accept!";
+/// firstStep.HelpText = "This is the End User License Agreement.";
+/// 
+/// // Add 2nd step
+/// var secondStep = new WizardStep ("Second Step");
+/// wizard.AddStep(secondStep);
+/// secondStep.HelpText = "This is the help text for the Second Step.";
+/// var lbl = new Label ("Name:") { AutoSize = true };
+/// secondStep.Add(lbl);
+/// 
+/// var name = new TextField () { X = Pos.Right (lbl) + 1, Width = Dim.Fill () - 1 };
+/// secondStep.Add(name);
+/// 
+/// wizard.Finished += (args) =>
+/// {
+///     MessageBox.Query("Wizard", $"Finished. The Name entered is '{name.Text}'", "Ok");
+///     Application.RequestStop();
+/// };
+/// 
+/// Application.Top.Add (wizard);
+/// Application.Run ();
+/// Application.Shutdown ();
+/// </code>
+/// </example>
+public class Wizard : Dialog {
 	/// <summary>
-	/// Provides navigation and a user interface (UI) to collect related data across multiple steps. Each step (<see cref="WizardStep"/>) can host 
-	/// arbitrary <see cref="View"/>s, much like a <see cref="Dialog"/>. Each step also has a pane for help text. Along the
-	/// bottom of the Wizard view are customizable buttons enabling the user to navigate forward and backward through the Wizard. 
+	/// Initializes a new instance of the <see cref="Wizard"/> class using <see cref="LayoutStyle.Computed"/> positioning.
 	/// </summary>
 	/// <remarks>
-	/// The Wizard can be displayed either as a modal (pop-up) <see cref="Window"/> (like <see cref="Dialog"/>) or as an embedded <see cref="View"/>. 
-	/// 
-	/// By default, <see cref="Wizard.Modal"/> is <c>true</c>. In this case launch the Wizard with <c>Application.Run(wizard)</c>. 
-	/// 
-	/// See <see cref="Wizard.Modal"/> for more details.
+	/// The Wizard will be vertically and horizontally centered in the container.
+	/// After initialization use <c>X</c>, <c>Y</c>, <c>Width</c>, and <c>Height</c> change size and position.
 	/// </remarks>
-	/// <example>
-	/// <code>
-	/// using Terminal.Gui;
-	/// using System.Text;
-	/// 
-	/// Application.Init();
-	/// 
-	/// var wizard = new Wizard ($"Setup Wizard");
-	/// 
-	/// // Add 1st step
-	/// var firstStep = new WizardStep ("End User License Agreement");
-	/// wizard.AddStep(firstStep);
-	/// firstStep.NextButtonText = "Accept!";
-	/// firstStep.HelpText = "This is the End User License Agreement.";
-	/// 
-	/// // Add 2nd step
-	/// var secondStep = new WizardStep ("Second Step");
-	/// wizard.AddStep(secondStep);
-	/// secondStep.HelpText = "This is the help text for the Second Step.";
-	/// var lbl = new Label ("Name:") { AutoSize = true };
-	/// secondStep.Add(lbl);
-	/// 
-	/// var name = new TextField () { X = Pos.Right (lbl) + 1, Width = Dim.Fill () - 1 };
-	/// secondStep.Add(name);
-	/// 
-	/// wizard.Finished += (args) =>
-	/// {
-	///     MessageBox.Query("Wizard", $"Finished. The Name entered is '{name.Text}'", "Ok");
-	///     Application.RequestStop();
-	/// };
-	/// 
-	/// Application.Top.Add (wizard);
-	/// Application.Run ();
-	/// Application.Shutdown ();
-	/// </code>
-	/// </example>
-	public class Wizard : Dialog {
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Wizard"/> class using <see cref="LayoutStyle.Computed"/> positioning.
-		/// </summary>
-		/// <remarks>
-		/// The Wizard will be vertically and horizontally centered in the container.
-		/// After initialization use <c>X</c>, <c>Y</c>, <c>Width</c>, and <c>Height</c> change size and position.
-		/// </remarks>
-		public Wizard () : base ()
-		{
-
-			// Using Justify causes the Back and Next buttons to be hard justified against
-			// the left and right edge
-			ButtonAlignment = ButtonAlignments.Justify;
-			BorderStyle = LineStyle.Double;
-
-			//// Add a horiz separator
-			var separator = new LineView (Orientation.Horizontal) {
-				Y = Pos.AnchorEnd (2)
-			};
-			Add (separator);
-
-			// BUGBUG: Space is to work around https://github.com/gui-cs/Terminal.Gui/issues/1812
-			backBtn = new Button (Strings.wzBack) { AutoSize = true };
-			AddButton (backBtn);
-
-			nextfinishBtn = new Button (Strings.wzFinish) { AutoSize = true };
-			nextfinishBtn.IsDefault = true;
-			AddButton (nextfinishBtn);
-
-			backBtn.Clicked += BackBtn_Clicked;
-			nextfinishBtn.Clicked += NextfinishBtn_Clicked;
-
-			Loaded += Wizard_Loaded;
-			Closing += Wizard_Closing;
-			TitleChanged += Wizard_TitleChanged;
-
-			if (Modal) {
-				ClearKeyBinding (Command.QuitToplevel);
-				AddKeyBinding (Key.Esc, Command.QuitToplevel);
-			}
-			SetNeedsLayout ();
-
+	public Wizard () : base ()
+	{
+
+		// Using Justify causes the Back and Next buttons to be hard justified against
+		// the left and right edge
+		ButtonAlignment = ButtonAlignments.Justify;
+		BorderStyle = LineStyle.Double;
+
+		//// Add a horiz separator
+		var separator = new LineView (Orientation.Horizontal) {
+			Y = Pos.AnchorEnd (2)
+		};
+		Add (separator);
+
+		// BUGBUG: Space is to work around https://github.com/gui-cs/Terminal.Gui/issues/1812
+		backBtn = new Button (Strings.wzBack) { AutoSize = true };
+		AddButton (backBtn);
+
+		nextfinishBtn = new Button (Strings.wzFinish) { AutoSize = true };
+		nextfinishBtn.IsDefault = true;
+		AddButton (nextfinishBtn);
+
+		backBtn.Clicked += BackBtn_Clicked;
+		nextfinishBtn.Clicked += NextfinishBtn_Clicked;
+
+		Loaded += Wizard_Loaded;
+		Closing += Wizard_Closing;
+		TitleChanged += Wizard_TitleChanged;
+
+		if (Modal) {
+			KeyBindings.Clear (Command.QuitToplevel);
+			KeyBindings.Add (KeyCode.Esc, Command.QuitToplevel);
 		}
+		SetNeedsLayout ();
 
-		private void Wizard_TitleChanged (object sender, TitleEventArgs e)
-		{
-			if (string.IsNullOrEmpty (wizardTitle)) {
-				wizardTitle = e.NewTitle;
-			}
-		}
+	}
 
-		private void Wizard_Loaded (object sender, EventArgs args)
-		{
-			CurrentStep = GetFirstStep (); // gets the first step if CurrentStep == null
+	void Wizard_TitleChanged (object sender, TitleEventArgs e)
+	{
+		if (string.IsNullOrEmpty (wizardTitle)) {
+			wizardTitle = e.NewTitle;
 		}
+	}
 
-		private bool finishedPressed = false;
+	void Wizard_Loaded (object sender, EventArgs args) => CurrentStep = GetFirstStep (); // gets the first step if CurrentStep == null
 
-		private void Wizard_Closing (object sender, ToplevelClosingEventArgs obj)
-		{
-			if (!finishedPressed) {
-				var args = new WizardButtonEventArgs ();
-				Cancelled?.Invoke (this, args);
-			}
-		}
+	bool finishedPressed = false;
 
-		private void NextfinishBtn_Clicked (object sender, EventArgs e)
-		{
-			if (CurrentStep == GetLastStep ()) {
-				var args = new WizardButtonEventArgs ();
-				Finished?.Invoke (this, args);
-				if (!args.Cancel) {
-					finishedPressed = true;
-					if (IsCurrentTop) {
-						Application.RequestStop (this);
-					} else {
-						// Wizard was created as a non-modal (just added to another View). 
-						// Do nothing
-					}
-				}
-			} else {
-				var args = new WizardButtonEventArgs ();
-				MovingNext?.Invoke (this, args);
-				if (!args.Cancel) {
-					GoNext ();
-				}
-			}
+	void Wizard_Closing (object sender, ToplevelClosingEventArgs obj)
+	{
+		if (!finishedPressed) {
+			var args = new WizardButtonEventArgs ();
+			Cancelled?.Invoke (this, args);
 		}
+	}
 
-		/// <summary>
-		/// <see cref="Wizard"/> is derived from <see cref="Dialog"/> and Dialog causes <c>Esc</c> to call
-		/// <see cref="Application.RequestStop(Toplevel)"/>, closing the Dialog. Wizard overrides <see cref="Responder.ProcessKey(KeyEvent)"/>
-		/// to instead fire the <see cref="Cancelled"/> event when Wizard is being used as a non-modal (see <see cref="Wizard.Modal"/>.
-		/// See <see cref="Responder.ProcessKey(KeyEvent)"/> for more.
-		/// </summary>
-		/// <param name="kb"></param>
-		/// <returns></returns>
-		public override bool ProcessKey (KeyEvent kb)
-		{
-			if (!Modal) {
-				switch (kb.Key) {
-				case Key.Esc:
-					var args = new WizardButtonEventArgs ();
-					Cancelled?.Invoke (this, args);
-					return false;
+	void NextfinishBtn_Clicked (object sender, EventArgs e)
+	{
+		if (CurrentStep == GetLastStep ()) {
+			var args = new WizardButtonEventArgs ();
+			Finished?.Invoke (this, args);
+			if (!args.Cancel) {
+				finishedPressed = true;
+				if (IsCurrentTop) {
+					Application.RequestStop (this);
+				} else {
+					// Wizard was created as a non-modal (just added to another View). 
+					// Do nothing
 				}
 			}
-			return base.ProcessKey (kb);
+		} else {
+			var args = new WizardButtonEventArgs ();
+			MovingNext?.Invoke (this, args);
+			if (!args.Cancel) {
+				GoNext ();
+			}
 		}
+	}
 
-		/// <summary>
-		/// Causes the wizad to move to the next enabled step (or last step if <see cref="CurrentStep"/> is not set). 
-		/// If there is no previous step, does nothing.
-		/// </summary>
-		public void GoNext ()
-		{
-			var nextStep = GetNextStep ();
-			if (nextStep != null) {
-				GoToStep (nextStep);
+	/// <summary>
+	/// <see cref="Wizard"/> is derived from <see cref="Dialog"/> and Dialog causes <c>Esc</c> to call
+	/// <see cref="Application.RequestStop(Toplevel)"/>, closing the Dialog. Wizard overrides <see cref="OnProcessKeyDown"/>
+	/// to instead fire the <see cref="Cancelled"/> event when Wizard is being used as a non-modal (see <see cref="Wizard.Modal"/>.
+	/// </summary>
+	/// <param name="a"></param>
+	/// <returns></returns>
+	public override bool OnProcessKeyDown (Key a)
+	{
+		//// BUGBUG: Why is this not handled by a key binding???
+		if (!Modal) {
+			switch (a.KeyCode) {
+			// BUGBUG: This should be handled by Dialog 
+			case KeyCode.Esc:
+				var args = new WizardButtonEventArgs ();
+				Cancelled?.Invoke (this, args);
+				return false;
 			}
 		}
+		return false;
+	}
 
-		/// <summary>
-		/// Returns the next enabled <see cref="WizardStep"/> after the current step. Takes into account steps which
-		/// are disabled. If <see cref="CurrentStep"/> is <c>null</c> returns the first enabled step.
-		/// </summary>
-		/// <returns>The next step after the current step, if there is one; otherwise returns <c>null</c>, which 
-		/// indicates either there are no enabled steps or the current step is the last enabled step.</returns>
-		public WizardStep GetNextStep ()
-		{
-			LinkedListNode<WizardStep> step = null;
-			if (CurrentStep == null) {
-				// Get first step, assume it is next
-				step = steps.First;
-			} else {
-				// Get the step after current
-				step = steps.Find (CurrentStep);
-				if (step != null) {
-					step = step.Next;
-				}
-			}
+	/// <summary>
+	/// Causes the wizad to move to the next enabled step (or last step if <see cref="CurrentStep"/> is not set). 
+	/// If there is no previous step, does nothing.
+	/// </summary>
+	public void GoNext ()
+	{
+		var nextStep = GetNextStep ();
+		if (nextStep != null) {
+			GoToStep (nextStep);
+		}
+	}
 
-			// step now points to the potential next step
-			while (step != null) {
-				if (step.Value.Enabled) {
-					return step.Value;
-				}
+	/// <summary>
+	/// Returns the next enabled <see cref="WizardStep"/> after the current step. Takes into account steps which
+	/// are disabled. If <see cref="CurrentStep"/> is <c>null</c> returns the first enabled step.
+	/// </summary>
+	/// <returns>The next step after the current step, if there is one; otherwise returns <c>null</c>, which 
+	/// indicates either there are no enabled steps or the current step is the last enabled step.</returns>
+	public WizardStep GetNextStep ()
+	{
+		LinkedListNode<WizardStep> step = null;
+		if (CurrentStep == null) {
+			// Get first step, assume it is next
+			step = steps.First;
+		} else {
+			// Get the step after current
+			step = steps.Find (CurrentStep);
+			if (step != null) {
 				step = step.Next;
 			}
-			return null;
 		}
 
-		private void BackBtn_Clicked (object sender, EventArgs e)
-		{
-			var args = new WizardButtonEventArgs ();
-			MovingBack?.Invoke (this, args);
-			if (!args.Cancel) {
-				GoBack ();
+		// step now points to the potential next step
+		while (step != null) {
+			if (step.Value.Enabled) {
+				return step.Value;
 			}
+			step = step.Next;
 		}
+		return null;
+	}
 
-		/// <summary>
-		/// Causes the wizad to move to the previous enabled step (or first step if <see cref="CurrentStep"/> is not set). 
-		/// If there is no previous step, does nothing.
-		/// </summary>
-		public void GoBack ()
-		{
-			var previous = GetPreviousStep ();
-			if (previous != null) {
-				GoToStep (previous);
-			}
+	void BackBtn_Clicked (object sender, EventArgs e)
+	{
+		var args = new WizardButtonEventArgs ();
+		MovingBack?.Invoke (this, args);
+		if (!args.Cancel) {
+			GoBack ();
 		}
+	}
 
-		/// <summary>
-		/// Returns the first enabled <see cref="WizardStep"/> before the current step. Takes into account steps which
-		/// are disabled. If <see cref="CurrentStep"/> is <c>null</c> returns the last enabled step.
-		/// </summary>
-		/// <returns>The first step ahead of the current step, if there is one; otherwise returns <c>null</c>, which 
-		/// indicates either there are no enabled steps or the current step is the first enabled step.</returns>
-		public WizardStep GetPreviousStep ()
-		{
-			LinkedListNode<WizardStep> step = null;
-			if (CurrentStep == null) {
-				// Get last step, assume it is previous
-				step = steps.Last;
-			} else {
-				// Get the step before current
-				step = steps.Find (CurrentStep);
-				if (step != null) {
-					step = step.Previous;
-				}
-			}
+	/// <summary>
+	/// Causes the wizad to move to the previous enabled step (or first step if <see cref="CurrentStep"/> is not set). 
+	/// If there is no previous step, does nothing.
+	/// </summary>
+	public void GoBack ()
+	{
+		var previous = GetPreviousStep ();
+		if (previous != null) {
+			GoToStep (previous);
+		}
+	}
 
-			// step now points to the potential previous step
-			while (step != null) {
-				if (step.Value.Enabled) {
-					return step.Value;
-				}
+	/// <summary>
+	/// Returns the first enabled <see cref="WizardStep"/> before the current step. Takes into account steps which
+	/// are disabled. If <see cref="CurrentStep"/> is <c>null</c> returns the last enabled step.
+	/// </summary>
+	/// <returns>The first step ahead of the current step, if there is one; otherwise returns <c>null</c>, which 
+	/// indicates either there are no enabled steps or the current step is the first enabled step.</returns>
+	public WizardStep GetPreviousStep ()
+	{
+		LinkedListNode<WizardStep> step = null;
+		if (CurrentStep == null) {
+			// Get last step, assume it is previous
+			step = steps.Last;
+		} else {
+			// Get the step before current
+			step = steps.Find (CurrentStep);
+			if (step != null) {
 				step = step.Previous;
 			}
-			return null;
 		}
 
-		/// <summary>
-		/// Returns the first enabled step in the Wizard
-		/// </summary>
-		/// <returns>The last enabled step</returns>
-		public WizardStep GetFirstStep ()
-		{
-			return steps.FirstOrDefault (s => s.Enabled);
+		// step now points to the potential previous step
+		while (step != null) {
+			if (step.Value.Enabled) {
+				return step.Value;
+			}
+			step = step.Previous;
 		}
+		return null;
+	}
 
-		/// <summary>
-		/// Returns the last enabled step in the Wizard
-		/// </summary>
-		/// <returns>The last enabled step</returns>
-		public WizardStep GetLastStep ()
-		{
-			return steps.LastOrDefault (s => s.Enabled);
-		}
+	/// <summary>
+	/// Returns the first enabled step in the Wizard
+	/// </summary>
+	/// <returns>The last enabled step</returns>
+	public WizardStep GetFirstStep () => steps.FirstOrDefault (s => s.Enabled);
 
-		private LinkedList<WizardStep> steps = new LinkedList<WizardStep> ();
-		private WizardStep currentStep = null;
-
-		/// <summary>
-		/// If the <see cref="CurrentStep"/> is not the first step in the wizard, this button causes
-		/// the <see cref="MovingBack"/> event to be fired and the wizard moves to the previous step. 
-		/// </summary>
-		/// <remarks>
-		/// Use the <see cref="MovingBack"></see> event to be notified when the user attempts to go back.
-		/// </remarks>
-		public Button BackButton { get => backBtn; }
-		private Button backBtn;
-
-		/// <summary>
-		/// If the <see cref="CurrentStep"/> is the last step in the wizard, this button causes
-		/// the <see cref="Finished"/> event to be fired and the wizard to close. If the step is not the last step,
-		/// the <see cref="MovingNext"/> event will be fired and the wizard will move next step. 
-		/// </summary>
-		/// <remarks>
-		/// Use the <see cref="MovingNext"></see> and <see cref="Finished"></see> events to be notified 
-		/// when the user attempts go to the next step or finish the wizard.
-		/// </remarks>
-		public Button NextFinishButton { get => nextfinishBtn; }
-		private Button nextfinishBtn;
-
-		/// <summary>
-		/// Adds a step to the wizard. The Next and Back buttons navigate through the added steps in the
-		/// order they were added.
-		/// </summary>
-		/// <param name="newStep"></param>
-		/// <remarks>The "Next..." button of the last step added will read "Finish" (unless changed from default).</remarks>
-		public void AddStep (WizardStep newStep)
-		{
-			SizeStep (newStep);
-
-			newStep.EnabledChanged += (s, e) => UpdateButtonsAndTitle ();
-			newStep.TitleChanged += (s, e) => UpdateButtonsAndTitle ();
-			steps.AddLast (newStep);
-			this.Add (newStep);
-			UpdateButtonsAndTitle ();
-		}
+	/// <summary>
+	/// Returns the last enabled step in the Wizard
+	/// </summary>
+	/// <returns>The last enabled step</returns>
+	public WizardStep GetLastStep () => steps.LastOrDefault (s => s.Enabled);
 
-		///// <summary>
-		///// The title of the Wizard, shown at the top of the Wizard with " - currentStep.Title" appended.
-		///// </summary>
-		///// <remarks>
-		///// The Title is only displayed when the <see cref="Wizard"/> <see cref="Wizard.Modal"/> is set to <c>false</c>.
-		///// </remarks>
-		//public new string Title {
-		//	get {
-		//		// The base (Dialog) Title holds the full title ("Wizard Title - Step Title")
-		//		return base.Title;
-		//	}
-		//	set {
-		//		wizardTitle = value;
-		//		base.Title = $"{wizardTitle}{(steps.Count > 0 && currentStep != null ? " - " + currentStep.Title : string.Empty)}";
-		//	}
-		//}
-		private string wizardTitle = string.Empty;
-
-		/// <summary>
-		/// Raised when the Back button in the <see cref="Wizard"/> is clicked. The Back button is always
-		/// the first button in the array of Buttons passed to the <see cref="Wizard"/> constructor, if any.
-		/// </summary>
-		public event EventHandler<WizardButtonEventArgs> MovingBack;
-
-		/// <summary>
-		/// Raised when the Next/Finish button in the <see cref="Wizard"/> is clicked (or the user presses Enter). 
-		/// The Next/Finish button is always the last button in the array of Buttons passed to the <see cref="Wizard"/> constructor, 
-		/// if any. This event is only raised if the <see cref="CurrentStep"/> is the last Step in the Wizard flow 
-		/// (otherwise the <see cref="Finished"/> event is raised).
-		/// </summary>
-		public event EventHandler<WizardButtonEventArgs> MovingNext;
-
-		/// <summary>
-		/// Raised when the Next/Finish button in the <see cref="Wizard"/> is clicked. The Next/Finish button is always
-		/// the last button in the array of Buttons passed to the <see cref="Wizard"/> constructor, if any. This event is only
-		/// raised if the <see cref="CurrentStep"/> is the last Step in the Wizard flow 
-		/// (otherwise the <see cref="Finished"/> event is raised).
-		/// </summary>
-		public event EventHandler<WizardButtonEventArgs> Finished;
-
-		/// <summary>
-		/// Raised when the user has cancelled the <see cref="Wizard"/> by pressin the Esc key. 
-		/// To prevent a modal (<see cref="Wizard.Modal"/> is <c>true</c>) Wizard from
-		/// closing, cancel the event by setting <see cref="WizardButtonEventArgs.Cancel"/> to 
-		/// <c>true</c> before returning from the event handler.
-		/// </summary>
-		public event EventHandler<WizardButtonEventArgs> Cancelled;
-
-		/// <summary>
-		/// This event is raised when the current <see cref="CurrentStep"/>) is about to change. Use <see cref="StepChangeEventArgs.Cancel"/> 
-		/// to abort the transition.
-		/// </summary>
-		public event EventHandler<StepChangeEventArgs> StepChanging;
-
-		/// <summary>
-		/// This event is raised after the <see cref="Wizard"/> has changed the <see cref="CurrentStep"/>. 
-		/// </summary>
-		public event EventHandler<StepChangeEventArgs> StepChanged;
-
-		/// <summary>
-		/// Gets or sets the currently active <see cref="WizardStep"/>.
-		/// </summary>
-		public WizardStep CurrentStep {
-			get => currentStep;
-			set {
-				GoToStep (value);
-			}
-		}
+	LinkedList<WizardStep> steps = new ();
+	WizardStep currentStep = null;
 
-		/// <summary>
-		/// Called when the <see cref="Wizard"/> is about to transition to another <see cref="WizardStep"/>. Fires the <see cref="StepChanging"/> event. 
-		/// </summary>
-		/// <param name="oldStep">The step the Wizard is about to change from</param>
-		/// <param name="newStep">The step the Wizard is about to change to</param>
-		/// <returns>True if the change is to be cancelled.</returns>
-		public virtual bool OnStepChanging (WizardStep oldStep, WizardStep newStep)
-		{
-			var args = new StepChangeEventArgs (oldStep, newStep);
-			StepChanging?.Invoke (this, args);
-			return args.Cancel;
-		}
+	/// <summary>
+	/// If the <see cref="CurrentStep"/> is not the first step in the wizard, this button causes
+	/// the <see cref="MovingBack"/> event to be fired and the wizard moves to the previous step. 
+	/// </summary>
+	/// <remarks>
+	/// Use the <see cref="MovingBack"></see> event to be notified when the user attempts to go back.
+	/// </remarks>
+	public Button BackButton => backBtn;
 
-		/// <summary>
-		/// Called when the <see cref="Wizard"/> has completed transition to a new <see cref="WizardStep"/>. Fires the <see cref="StepChanged"/> event. 
-		/// </summary>
-		/// <param name="oldStep">The step the Wizard changed from</param>
-		/// <param name="newStep">The step the Wizard has changed to</param>
-		/// <returns>True if the change is to be cancelled.</returns>
-		public virtual bool OnStepChanged (WizardStep oldStep, WizardStep newStep)
-		{
-			var args = new StepChangeEventArgs (oldStep, newStep);
-			StepChanged?.Invoke (this, args);
-			return args.Cancel;
-		}
+	Button backBtn;
 
-		/// <summary>
-		/// Changes to the specified <see cref="WizardStep"/>.
-		/// </summary>
-		/// <param name="newStep">The step to go to.</param>
-		/// <returns>True if the transition to the step succeeded. False if the step was not found or the operation was cancelled.</returns>
-		public bool GoToStep (WizardStep newStep)
-		{
-			if (OnStepChanging (currentStep, newStep) || (newStep != null && !newStep.Enabled)) {
-				return false;
-			}
+	/// <summary>
+	/// If the <see cref="CurrentStep"/> is the last step in the wizard, this button causes
+	/// the <see cref="Finished"/> event to be fired and the wizard to close. If the step is not the last step,
+	/// the <see cref="MovingNext"/> event will be fired and the wizard will move next step. 
+	/// </summary>
+	/// <remarks>
+	/// Use the <see cref="MovingNext"></see> and <see cref="Finished"></see> events to be notified 
+	/// when the user attempts go to the next step or finish the wizard.
+	/// </remarks>
+	public Button NextFinishButton => nextfinishBtn;
 
-			// Hide all but the new step
-			foreach (WizardStep step in steps) {
-				step.Visible = (step == newStep);
-				step.ShowHide ();
-			}
+	Button nextfinishBtn;
 
-			var oldStep = currentStep;
-			currentStep = newStep;
+	/// <summary>
+	/// Adds a step to the wizard. The Next and Back buttons navigate through the added steps in the
+	/// order they were added.
+	/// </summary>
+	/// <param name="newStep"></param>
+	/// <remarks>The "Next..." button of the last step added will read "Finish" (unless changed from default).</remarks>
+	public void AddStep (WizardStep newStep)
+	{
+		SizeStep (newStep);
+
+		newStep.EnabledChanged += (s, e) => UpdateButtonsAndTitle ();
+		newStep.TitleChanged += (s, e) => UpdateButtonsAndTitle ();
+		steps.AddLast (newStep);
+		Add (newStep);
+		UpdateButtonsAndTitle ();
+	}
 
-			UpdateButtonsAndTitle ();
+	///// <summary>
+	///// The title of the Wizard, shown at the top of the Wizard with " - currentStep.Title" appended.
+	///// </summary>
+	///// <remarks>
+	///// The Title is only displayed when the <see cref="Wizard"/> <see cref="Wizard.Modal"/> is set to <c>false</c>.
+	///// </remarks>
+	//public new string Title {
+	//	get {
+	//		// The base (Dialog) Title holds the full title ("Wizard Title - Step Title")
+	//		return base.Title;
+	//	}
+	//	set {
+	//		wizardTitle = value;
+	//		base.Title = $"{wizardTitle}{(steps.Count > 0 && currentStep != null ? " - " + currentStep.Title : string.Empty)}";
+	//	}
+	//}
+	string wizardTitle = string.Empty;
 
-			// Set focus to the nav buttons
-			if (backBtn.HasFocus) {
-				backBtn.SetFocus ();
-			} else {
-				nextfinishBtn.SetFocus ();
-			}
+	/// <summary>
+	/// Raised when the Back button in the <see cref="Wizard"/> is clicked. The Back button is always
+	/// the first button in the array of Buttons passed to the <see cref="Wizard"/> constructor, if any.
+	/// </summary>
+	public event EventHandler<WizardButtonEventArgs> MovingBack;
 
-			if (OnStepChanged (oldStep, currentStep)) {
-				// For correctness we do this, but it's meaningless because there's nothing to cancel
-				return false;
-			}
+	/// <summary>
+	/// Raised when the Next/Finish button in the <see cref="Wizard"/> is clicked (or the user presses Enter). 
+	/// The Next/Finish button is always the last button in the array of Buttons passed to the <see cref="Wizard"/> constructor, 
+	/// if any. This event is only raised if the <see cref="CurrentStep"/> is the last Step in the Wizard flow 
+	/// (otherwise the <see cref="Finished"/> event is raised).
+	/// </summary>
+	public event EventHandler<WizardButtonEventArgs> MovingNext;
+
+	/// <summary>
+	/// Raised when the Next/Finish button in the <see cref="Wizard"/> is clicked. The Next/Finish button is always
+	/// the last button in the array of Buttons passed to the <see cref="Wizard"/> constructor, if any. This event is only
+	/// raised if the <see cref="CurrentStep"/> is the last Step in the Wizard flow 
+	/// (otherwise the <see cref="Finished"/> event is raised).
+	/// </summary>
+	public event EventHandler<WizardButtonEventArgs> Finished;
+
+	/// <summary>
+	/// Raised when the user has cancelled the <see cref="Wizard"/> by pressin the Esc key. 
+	/// To prevent a modal (<see cref="Wizard.Modal"/> is <c>true</c>) Wizard from
+	/// closing, cancel the event by setting <see cref="WizardButtonEventArgs.Cancel"/> to 
+	/// <c>true</c> before returning from the event handler.
+	/// </summary>
+	public event EventHandler<WizardButtonEventArgs> Cancelled;
+
+	/// <summary>
+	/// This event is raised when the current <see cref="CurrentStep"/>) is about to change. Use <see cref="StepChangeEventArgs.Cancel"/> 
+	/// to abort the transition.
+	/// </summary>
+	public event EventHandler<StepChangeEventArgs> StepChanging;
+
+	/// <summary>
+	/// This event is raised after the <see cref="Wizard"/> has changed the <see cref="CurrentStep"/>. 
+	/// </summary>
+	public event EventHandler<StepChangeEventArgs> StepChanged;
+
+	/// <summary>
+	/// Gets or sets the currently active <see cref="WizardStep"/>.
+	/// </summary>
+	public WizardStep CurrentStep {
+		get => currentStep;
+		set => GoToStep (value);
+	}
+
+	/// <summary>
+	/// Called when the <see cref="Wizard"/> is about to transition to another <see cref="WizardStep"/>. Fires the <see cref="StepChanging"/> event. 
+	/// </summary>
+	/// <param name="oldStep">The step the Wizard is about to change from</param>
+	/// <param name="newStep">The step the Wizard is about to change to</param>
+	/// <returns>True if the change is to be cancelled.</returns>
+	public virtual bool OnStepChanging (WizardStep oldStep, WizardStep newStep)
+	{
+		var args = new StepChangeEventArgs (oldStep, newStep);
+		StepChanging?.Invoke (this, args);
+		return args.Cancel;
+	}
+
+	/// <summary>
+	/// Called when the <see cref="Wizard"/> has completed transition to a new <see cref="WizardStep"/>. Fires the <see cref="StepChanged"/> event. 
+	/// </summary>
+	/// <param name="oldStep">The step the Wizard changed from</param>
+	/// <param name="newStep">The step the Wizard has changed to</param>
+	/// <returns>True if the change is to be cancelled.</returns>
+	public virtual bool OnStepChanged (WizardStep oldStep, WizardStep newStep)
+	{
+		var args = new StepChangeEventArgs (oldStep, newStep);
+		StepChanged?.Invoke (this, args);
+		return args.Cancel;
+	}
+
+	/// <summary>
+	/// Changes to the specified <see cref="WizardStep"/>.
+	/// </summary>
+	/// <param name="newStep">The step to go to.</param>
+	/// <returns>True if the transition to the step succeeded. False if the step was not found or the operation was cancelled.</returns>
+	public bool GoToStep (WizardStep newStep)
+	{
+		if (OnStepChanging (currentStep, newStep) || newStep != null && !newStep.Enabled) {
+			return false;
+		}
 
-			return true;
+		// Hide all but the new step
+		foreach (var step in steps) {
+			step.Visible = step == newStep;
+			step.ShowHide ();
 		}
 
-		private void UpdateButtonsAndTitle ()
-		{
-			if (CurrentStep == null) return;
+		var oldStep = currentStep;
+		currentStep = newStep;
 
-			Title = $"{wizardTitle}{(steps.Count > 0 ? " - " + CurrentStep.Title : string.Empty)}";
+		UpdateButtonsAndTitle ();
 
-			// Configure the Back button
-			backBtn.Text = CurrentStep.BackButtonText != string.Empty ? CurrentStep.BackButtonText : Strings.wzBack; // "_Back";
-			backBtn.Visible = (CurrentStep != GetFirstStep ());
+		// Set focus to the nav buttons
+		if (backBtn.HasFocus) {
+			backBtn.SetFocus ();
+		} else {
+			nextfinishBtn.SetFocus ();
+		}
 
-			// Configure the Next/Finished button
-			if (CurrentStep == GetLastStep ()) {
-				nextfinishBtn.Text = CurrentStep.NextButtonText != string.Empty ? CurrentStep.NextButtonText : Strings.wzFinish; // "Fi_nish";
-			} else {
-				nextfinishBtn.Text = CurrentStep.NextButtonText != string.Empty ? CurrentStep.NextButtonText : Strings.wzNext; // "_Next...";
-			}
+		if (OnStepChanged (oldStep, currentStep)) {
+			// For correctness we do this, but it's meaningless because there's nothing to cancel
+			return false;
+		}
 
-			SizeStep (CurrentStep);
+		return true;
+	}
 
-			SetNeedsLayout ();
-			LayoutSubviews ();
-			Draw ();
+	void UpdateButtonsAndTitle ()
+	{
+		if (CurrentStep == null) {
+			return;
 		}
 
-		private void SizeStep (WizardStep step)
-		{
-			if (Modal) {
-				// If we're modal, then we expand the WizardStep so that the top and side 
-				// borders and not visible. The bottom border is the separator above the buttons.
-				step.X = step.Y = 0;
-				step.Height = Dim.Fill (2); // for button frame
-				step.Width = Dim.Fill (0);
-			} else {
-				// If we're not a modal, then we show the border around the WizardStep
-				step.X = step.Y = 0;
-				step.Height = Dim.Fill (1); // for button frame
-				step.Width = Dim.Fill (0);
-			}
+		Title = $"{wizardTitle}{(steps.Count > 0 ? " - " + CurrentStep.Title : string.Empty)}";
+
+		// Configure the Back button
+		backBtn.Text = CurrentStep.BackButtonText != string.Empty ? CurrentStep.BackButtonText : Strings.wzBack; // "_Back";
+		backBtn.Visible = CurrentStep != GetFirstStep ();
+
+		// Configure the Next/Finished button
+		if (CurrentStep == GetLastStep ()) {
+			nextfinishBtn.Text = CurrentStep.NextButtonText != string.Empty ? CurrentStep.NextButtonText : Strings.wzFinish; // "Fi_nish";
+		} else {
+			nextfinishBtn.Text = CurrentStep.NextButtonText != string.Empty ? CurrentStep.NextButtonText : Strings.wzNext; // "_Next...";
 		}
 
-		/// <summary>
-		/// Determines whether the <see cref="Wizard"/> is displayed as modal pop-up or not.
-		/// 
-		/// The default is <see langword="true"/>. The Wizard will be shown with a frame and title and will behave like
-		/// any <see cref="Toplevel"/> window.
-		/// 
-		/// If set to <c>false</c> the Wizard will have no frame and will behave like any embedded <see cref="View"/>.
-		/// 
-		/// To use Wizard as an embedded View 
-		/// <list type="number">
-		/// <item><description>Set <see cref="Modal"/> to <c>false</c>.</description></item>
-		/// <item><description>Add the Wizard to a containing view with <see cref="View.Add(View)"/>.</description></item>
-		/// </list>
-		/// 
-		/// If a non-Modal Wizard is added to the application after <see cref="Application.Run(Func{Exception, bool})"/> has been called
-		/// the first step must be explicitly set by setting <see cref="CurrentStep"/> to <see cref="GetNextStep()"/>:
-		/// <code>
-		///    wizard.CurrentStep = wizard.GetNextStep();
-		/// </code>
-		/// </summary>
-		public new bool Modal {
-			get => base.Modal;
-			set {
-				base.Modal = value;
-				foreach (var step in steps) {
-					SizeStep (step);
-				}
-				if (base.Modal) {
-					ColorScheme = Colors.Dialog;
-					BorderStyle = LineStyle.Rounded;
+		SizeStep (CurrentStep);
+
+		SetNeedsLayout ();
+		LayoutSubviews ();
+		Draw ();
+	}
+
+	void SizeStep (WizardStep step)
+	{
+		if (Modal) {
+			// If we're modal, then we expand the WizardStep so that the top and side 
+			// borders and not visible. The bottom border is the separator above the buttons.
+			step.X = step.Y = 0;
+			step.Height = Dim.Fill (2); // for button frame
+			step.Width = Dim.Fill (0);
+		} else {
+			// If we're not a modal, then we show the border around the WizardStep
+			step.X = step.Y = 0;
+			step.Height = Dim.Fill (1); // for button frame
+			step.Width = Dim.Fill (0);
+		}
+	}
+
+	/// <summary>
+	/// Determines whether the <see cref="Wizard"/> is displayed as modal pop-up or not.
+	/// 
+	/// The default is <see langword="true"/>. The Wizard will be shown with a frame and title and will behave like
+	/// any <see cref="Toplevel"/> window.
+	/// 
+	/// If set to <c>false</c> the Wizard will have no frame and will behave like any embedded <see cref="View"/>.
+	/// 
+	/// To use Wizard as an embedded View 
+	/// <list type="number">
+	/// <item><description>Set <see cref="Modal"/> to <c>false</c>.</description></item>
+	/// <item><description>Add the Wizard to a containing view with <see cref="View.Add(View)"/>.</description></item>
+	/// </list>
+	/// 
+	/// If a non-Modal Wizard is added to the application after <see cref="Application.Run(Func{Exception, bool})"/> has been called
+	/// the first step must be explicitly set by setting <see cref="CurrentStep"/> to <see cref="GetNextStep()"/>:
+	/// <code>
+	///    wizard.CurrentStep = wizard.GetNextStep();
+	/// </code>
+	/// </summary>
+	public new bool Modal {
+		get => base.Modal;
+		set {
+			base.Modal = value;
+			foreach (var step in steps) {
+				SizeStep (step);
+			}
+			if (base.Modal) {
+				ColorScheme = Colors.Dialog;
+				BorderStyle = LineStyle.Rounded;
+			} else {
+				if (SuperView != null) {
+					ColorScheme = SuperView.ColorScheme;
 				} else {
-					if (SuperView != null) {
-						ColorScheme = SuperView.ColorScheme;
-					} else {
-						ColorScheme = Colors.Base;
-					}
-					CanFocus = true;
-					BorderStyle = LineStyle.None;
+					ColorScheme = Colors.Base;
 				}
+				CanFocus = true;
+				BorderStyle = LineStyle.None;
 			}
 		}
 	}

+ 9 - 9
UICatalog/KeyBindingsDialog.cs

@@ -8,8 +8,8 @@ using Terminal.Gui;
 namespace UICatalog {
 
 	class KeyBindingsDialog : Dialog {
-
-		static Dictionary<Command,Key> CurrentBindings = new Dictionary<Command,Key>();
+		// TODO: Update to use Key instead of KeyCode
+		static Dictionary<Command,KeyCode> CurrentBindings = new Dictionary<Command,KeyCode>();
 		private Command[] commands;
 		private ListView commandsListView;
 		private Label keyLabel;
@@ -28,7 +28,7 @@ namespace UICatalog {
 			Dictionary<View, bool> knownViews = new Dictionary<View, bool> ();
 
 			private object lockKnownViews = new object ();
-			private Dictionary<Command, Key> keybindings;
+			private Dictionary<Command, KeyCode> keybindings;
 
 			public ViewTracker (View top)
 			{
@@ -70,7 +70,7 @@ namespace UICatalog {
 				Instance = new ViewTracker (Application.Top);
 			}
 
-			internal void StartUsingNewKeyMap (Dictionary<Command, Key> currentBindings)
+			internal void StartUsingNewKeyMap (Dictionary<Command, KeyCode> currentBindings)
 			{
 				lock (lockKnownViews) {
 
@@ -109,8 +109,8 @@ namespace UICatalog {
 						if(supported.Contains(kvp.Key))
 						{
 							// if the key was bound to any other commands clear that
-							view.ClearKeyBinding (kvp.Key);
-							view.AddKeyBinding (kvp.Value,kvp.Key);
+							view.KeyBindings.Remove (kvp.Value);
+							view.KeyBindings.Add (kvp.Value,kvp.Key);
 						}
 
 						// mark that we have done this view so don't need to set keybindings again on it
@@ -176,12 +176,12 @@ namespace UICatalog {
 		private void RemapKey (object sender, EventArgs e)
 		{
 			var cmd = commands [commandsListView.SelectedItem];
-			Key? key = null;
+			KeyCode? key = null;
 
 			// prompt user to hit a key
 			var dlg = new Dialog () { Title = "Enter Key" };
-			dlg.KeyPressed += (s, k) => {
-				key = k.KeyEvent.Key;
+			dlg.KeyDown += (s, k) => {
+				key = k.KeyCode;
 				Application.RequestStop ();
 			};
 			Application.Run (dlg);

+ 4 - 0
UICatalog/Properties/launchSettings.json

@@ -60,6 +60,10 @@
     },
     "Docker": {
       "commandName": "Docker"
+    },
+    "MenuBarScenario": {
+      "commandName": "Project",
+      "commandLineArgs": "MenuBar"
     }
   }
 }

+ 10 - 10
UICatalog/Scenarios/ASCIICustomButton.cs

@@ -21,7 +21,7 @@ namespace UICatalog.Scenarios {
 						CheckType = MenuItemCheckStyle.Checked
 					},
 					null,
-					new MenuItem("Quit", "",() => Application.RequestStop(),null,null, Application.QuitKey)
+					new MenuItem("Quit", "",() => Application.RequestStop(),null,null, (KeyCode)Application.QuitKey)
 				})
 			});
 			Application.Top.Add (menu, _scrollViewTestWindow);
@@ -193,7 +193,7 @@ namespace UICatalog.Scenarios {
 					};
 				}
 
-				scrollView.ClearKeyBindings ();
+				scrollView.KeyBindings.Clear ();
 
 				buttons = new List<Button> ();
 				Button prevButton = null;
@@ -205,7 +205,7 @@ namespace UICatalog.Scenarios {
 					button.Clicked += Button_Clicked;
 					button.PointerEnter += Button_PointerEnter;
 					button.MouseClick += Button_MouseClick;
-					button.KeyPressed += Button_KeyPress;
+					button.KeyDown += Button_KeyPress;
 					scrollView.Add (button);
 					buttons.Add (button);
 					prevButton = button;
@@ -215,7 +215,7 @@ namespace UICatalog.Scenarios {
 				closeButton.Clicked += Button_Clicked;
 				closeButton.PointerEnter += Button_PointerEnter;
 				closeButton.MouseClick += Button_MouseClick;
-				closeButton.KeyPressed += Button_KeyPress;
+				closeButton.KeyDown += Button_KeyPress;
 				scrollView.Add (closeButton);
 				buttons.Add (closeButton);
 
@@ -231,27 +231,27 @@ namespace UICatalog.Scenarios {
 				}
 			}
 
-			private void Button_KeyPress (object sender, KeyEventEventArgs obj)
+			private void Button_KeyPress (object sender, Key obj)
 			{
-				switch (obj.KeyEvent.Key) {
-				case Key.End:
+				switch (obj.KeyCode) {
+				case KeyCode.End:
 					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
 						 -(scrollView.ContentSize.Height - scrollView.Frame.Height
 						 + (scrollView.ShowHorizontalScrollIndicator ? 1 : 0)));
 					obj.Handled = true;
 					return;
-				case Key.Home:
+				case KeyCode.Home:
 					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X, 0);
 					obj.Handled = true;
 					return;
-				case Key.PageDown:
+				case KeyCode.PageDown:
 					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
 						 Math.Max (scrollView.ContentOffset.Y - scrollView.Frame.Height,
 						 -(scrollView.ContentSize.Height - scrollView.Frame.Height
 						 + (scrollView.ShowHorizontalScrollIndicator ? 1 : 0))));
 					obj.Handled = true;
 					return;
-				case Key.PageUp:
+				case KeyCode.PageUp:
 					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
 						 Math.Min (scrollView.ContentOffset.Y + scrollView.Frame.Height, 0));
 					obj.Handled = true;

+ 2 - 2
UICatalog/Scenarios/AllViewsTester.cs

@@ -52,11 +52,11 @@ namespace UICatalog.Scenarios {
 		{
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit()),
-				new StatusItem(Key.F2, "~F2~ Toggle Frame Ruler", () => {
+				new StatusItem(KeyCode.F2, "~F2~ Toggle Frame Ruler", () => {
 					ConsoleDriver.Diagnostics ^= ConsoleDriver.DiagnosticFlags.FrameRuler;
 					Application.Top.SetNeedsDisplay ();
 				}),
-				new StatusItem(Key.F3, "~F3~ Toggle Frame Padding", () => {
+				new StatusItem(KeyCode.F3, "~F3~ Toggle Frame Padding", () => {
 					ConsoleDriver.Diagnostics ^= ConsoleDriver.DiagnosticFlags.FramePadding;
 					Application.Top.SetNeedsDisplay ();
 				}),

+ 7 - 7
UICatalog/Scenarios/BackgroundWorkerCollection.cs

@@ -33,10 +33,10 @@ namespace UICatalog.Scenarios {
 
 				menu = new MenuBar (new MenuBarItem [] {
 					new MenuBarItem ("_Options", new MenuItem [] {
-						new MenuItem ("_Run Worker", "", () => workerApp.RunWorker(), null, null, Key.CtrlMask | Key.R),
-						new MenuItem ("_Cancel Worker", "", () => workerApp.CancelWorker(), null, null, Key.CtrlMask | Key.C),
+						new MenuItem ("_Run Worker", "", () => workerApp.RunWorker(), null, null, KeyCode.CtrlMask | KeyCode.R),
+						new MenuItem ("_Cancel Worker", "", () => workerApp.CancelWorker(), null, null, KeyCode.CtrlMask | KeyCode.C),
 						null,
-						new MenuItem ("_Quit", "", () => Quit(), null, null, Application.QuitKey)
+						new MenuItem ("_Quit", "", () => Quit(), null, null, (KeyCode)Application.QuitKey)
 					}),
 					new MenuBarItem ("_View", new MenuItem [] { }),
 					new MenuBarItem ("_Window", new MenuItem [] { })
@@ -46,8 +46,8 @@ namespace UICatalog.Scenarios {
 
 				var statusBar = new StatusBar (new [] {
 					new StatusItem(Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit()),
-					new StatusItem(Key.CtrlMask | Key.R, "~^R~ Run Worker", () => workerApp.RunWorker()),
-					new StatusItem(Key.CtrlMask | Key.C, "~^C~ Cancel Worker", () => workerApp.CancelWorker())
+					new StatusItem(KeyCode.CtrlMask | KeyCode.R, "~^R~ Run Worker", () => workerApp.RunWorker()),
+					new StatusItem(KeyCode.CtrlMask | KeyCode.C, "~^C~ Cancel Worker", () => workerApp.CancelWorker())
 				});
 				Add (statusBar);
 
@@ -338,8 +338,8 @@ namespace UICatalog.Scenarios {
 				close.Clicked += OnReportClosed;
 				Add (close);
 
-				KeyPressed += (s, e) => {
-					if (e.KeyEvent.Key == Key.Esc) {
+				KeyDown += (s, e) => {
+					if (e.KeyCode == KeyCode.Esc) {
 						OnReportClosed (this, EventArgs.Empty);
 					}
 				};

+ 242 - 244
UICatalog/Scenarios/Buttons.cs

@@ -1,278 +1,276 @@
 using System.Text;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
 using Terminal.Gui;
 
-namespace UICatalog.Scenarios {
-	[ScenarioMetadata (Name: "Buttons", Description: "Demonstrates all sorts of Buttons.")]
-	[ScenarioCategory ("Controls")]
-	[ScenarioCategory ("Layout")]
-	public class Buttons : Scenario {
-		public override void Setup ()
-		{
-			// Add a label & text field so we can demo IsDefault
-			var editLabel = new Label ("TextField (to demo IsDefault):") {
-				X = 0,
-				Y = 0,
-			};
-			Win.Add (editLabel);
-			// Add a TextField using Absolute layout. 
-			var edit = new TextField (31, 0, 15, "");
-			Win.Add (edit);
+namespace UICatalog.Scenarios;
+[ScenarioMetadata (Name: "Buttons", Description: "Demonstrates all sorts of Buttons.")]
+[ScenarioCategory ("Controls")]
+[ScenarioCategory ("Layout")]
+public class Buttons : Scenario {
+	public override void Setup ()
+	{
+		// Add a label & text field so we can demo IsDefault
+		var editLabel = new Label ("TextField (to demo IsDefault):") {
+			X = 0,
+			Y = 0,
+			TabStop = true,
+		};
+		Win.Add (editLabel);
+		// Add a TextField using Absolute layout. 
+		var edit = new TextField (31, 0, 15, "") {
+			HotKey = Key.Y.WithAlt,
+		};
+		Win.Add (edit);
 
-			// This is the default button (IsDefault = true); if user presses ENTER in the TextField
-			// the scenario will quit
-			var defaultButton = new Button ("_Quit") {
-				X = Pos.Center (),
-				//TODO: Change to use Pos.AnchorEnd()
-				Y = Pos.Bottom (Win) - 3,
-				IsDefault = true,
-			};
-			defaultButton.Clicked += (s,e) => Application.RequestStop ();
-			Win.Add (defaultButton);
-
-			var swapButton = new Button (50, 0, "Swap Default (Absolute Layout)");
-			swapButton.Clicked += (s,e) => {
-				defaultButton.IsDefault = !defaultButton.IsDefault;
-				swapButton.IsDefault = !swapButton.IsDefault;
-			};
-			Win.Add (swapButton);
+		// This is the default button (IsDefault = true); if user presses ENTER in the TextField
+		// the scenario will quit
+		var defaultButton = new Button ("_Quit") {
+			X = Pos.Center (),
+			//TODO: Change to use Pos.AnchorEnd()
+			Y = Pos.Bottom (Win) - 3,
+			IsDefault = true,
+		};
+		defaultButton.Clicked += (s, e) => Application.RequestStop ();
+		Win.Add (defaultButton);
 
-			static void DoMessage (Button button, string txt)
-			{
-				button.Clicked += (s,e) => {
-					var btnText = button.Text;
-					MessageBox.Query ("Message", $"Did you click {txt}?", "Yes", "No");
-				};
-			}
+		var swapButton = new Button (50, 0, "Swap Default (Absolute Layout)");
+		swapButton.Clicked += (s, e) => {
+			defaultButton.IsDefault = !defaultButton.IsDefault;
+			swapButton.IsDefault = !swapButton.IsDefault;
+		};
+		Win.Add (swapButton);
 
-			var colorButtonsLabel = new Label ("Color Buttons:") {
-				X = 0,
-				Y = Pos.Bottom (editLabel) + 1,
+		static void DoMessage (Button button, string txt)
+		{
+			button.Clicked += (s, e) => {
+				var btnText = button.Text;
+				MessageBox.Query ("Message", $"Did you click {txt}?", "Yes", "No");
 			};
-			Win.Add (colorButtonsLabel);
-
-			//View prev = colorButtonsLabel;
-
-			//With this method there is no need to call Application.TopReady += () => Application.TopRedraw (Top.Bounds);
-			var x = Pos.Right (colorButtonsLabel) + 2;
-			foreach (var colorScheme in Colors.ColorSchemes) {
-				var colorButton = new Button ($"{colorScheme.Key}") {
-					ColorScheme = colorScheme.Value,
-					//X = Pos.Right (prev) + 2,
-					X = x,
-					Y = Pos.Y (colorButtonsLabel),
-				};
-				DoMessage (colorButton, colorButton.Text);
-				Win.Add (colorButton);
-				//prev = colorButton;
-				x += colorButton.Frame.Width + 2;
-			}
+		}
 
-			Button button;
-			Win.Add (button = new Button ("A super long _Button that will probably expose a bug in clipping or wrapping of text. Will it?") {
-				X = 2,
-				Y = Pos.Bottom (colorButtonsLabel) + 1,
-			});
-			DoMessage (button, button.Text);
+		var colorButtonsLabel = new Label ("Color Buttons:") {
+			X = 0,
+			Y = Pos.Bottom (editLabel) + 1,
+		};
+		Win.Add (colorButtonsLabel);
 
-			// Note the 'N' in 'Newline' will be the hotkey
-			Win.Add (button = new Button ("a Newline\nin the button") {
-				X = 2,
-				Y = Pos.Bottom (button) + 1,
-			});
-			button.Clicked += (s,e) => MessageBox.Query ("Message", "Question?", "Yes", "No");
+		//View prev = colorButtonsLabel;
 
-			var textChanger = new Button ("Te_xt Changer") {
-				X = 2,
-				Y = Pos.Bottom (button) + 1,
+		//With this method there is no need to call Application.TopReady += () => Application.TopRedraw (Top.Bounds);
+		var x = Pos.Right (colorButtonsLabel) + 2;
+		foreach (var colorScheme in Colors.ColorSchemes) {
+			var colorButton = new Button ($"{colorScheme.Key}") {
+				ColorScheme = colorScheme.Value,
+				//X = Pos.Right (prev) + 2,
+				X = x,
+				Y = Pos.Y (colorButtonsLabel),
 			};
-			Win.Add (textChanger);
-			textChanger.Clicked += (s,e) => textChanger.Text += "!";
+			DoMessage (colorButton, colorButton.Text);
+			Win.Add (colorButton);
+			//prev = colorButton;
+			x += colorButton.Frame.Width + 2;
+		}
 
-			Win.Add (button = new Button ("Lets see if this will move as \"Text Changer\" grows") {
-				X = Pos.Right (textChanger) + 2,
-				Y = Pos.Y (textChanger),
-			});
+		Button button;
+		Win.Add (button = new Button ("A super l_öng Button that will probably expose a bug in clipping or wrapping of text. Will it?") {
+			X = 2,
+			Y = Pos.Bottom (colorButtonsLabel) + 1,
+		});
+		DoMessage (button, button.Text);
 
-			var removeButton = new Button ("Remove this button") {
-				X = 2,
-				Y = Pos.Bottom (button) + 1,
-				ColorScheme = Colors.Error
-			};
-			Win.Add (removeButton);
-			// This in interesting test case because `moveBtn` and below are laid out relative to this one!
-			removeButton.Clicked += (s,e) => {
-				// Now this throw a InvalidOperationException on the TopologicalSort method as is expected.
-				//Win.Remove (removeButton);
+		// Note the 'N' in 'Newline' will be the hotkey
+		Win.Add (button = new Button ("a Newline\nin the button") {
+			X = 2,
+			Y = Pos.Bottom (button) + 1,
+		});
+		button.Clicked += (s, e) => MessageBox.Query ("Message", "Question?", "Yes", "No");
 
-				removeButton.Visible = false;
-			};
+		var textChanger = new Button ("Te_xt Changer") {
+			X = 2,
+			Y = Pos.Bottom (button) + 1,
+		};
+		Win.Add (textChanger);
+		textChanger.Clicked += (s, e) => textChanger.Text += "!";
 
-			var computedFrame = new FrameView ("Computed Layout") {
-				X = 0,
-				Y = Pos.Bottom (removeButton) + 1,
-				Width = Dim.Percent (50),
-				Height = 5
-			};
-			Win.Add (computedFrame);
+		Win.Add (button = new Button ("Lets see if this will move as \"Text Changer\" grows") {
+			X = Pos.Right (textChanger) + 2,
+			Y = Pos.Y (textChanger),
+		});
 
-			// Demonstrates how changing the View.Frame property can move Views
-			var moveBtn = new Button ("Move This \u263b Button _via Pos") {
-				X = 0,
-				Y = Pos.Center () - 1,
-				Width = 30,
-				ColorScheme = Colors.Error,
-			};
-			moveBtn.Clicked += (s,e) => {
-				moveBtn.X = moveBtn.Frame.X + 5;
-				// This is already fixed with the call to SetNeedDisplay() in the Pos Dim.
-				//computedFrame.LayoutSubviews (); // BUGBUG: This call should not be needed. View.X is not causing relayout correctly
-			};
-			computedFrame.Add (moveBtn);
+		var removeButton = new Button ("Remove this button") {
+			X = 2,
+			Y = Pos.Bottom (button) + 1,
+			ColorScheme = Colors.Error
+		};
+		Win.Add (removeButton);
+		// This in interesting test case because `moveBtn` and below are laid out relative to this one!
+		removeButton.Clicked += (s, e) => {
+			// Now this throw a InvalidOperationException on the TopologicalSort method as is expected.
+			//Win.Remove (removeButton);
 
-			// Demonstrates how changing the View.Frame property can SIZE Views (#583)
-			var sizeBtn = new Button ("Size This \u263a Button _via Pos") {
-				X = 0,
-				Y = Pos.Center () + 1,
-				Width = 30,
-				ColorScheme = Colors.Error,
-			};
-			sizeBtn.Clicked += (s,e) => {
-				sizeBtn.Width = sizeBtn.Frame.Width + 5;
-				//computedFrame.LayoutSubviews (); // FIXED: This call should not be needed. View.X is not causing relayout correctly
-			};
-			computedFrame.Add (sizeBtn);
+			removeButton.Visible = false;
+		};
 
-			var absoluteFrame = new FrameView ("Absolute Layout") {
-				X = Pos.Right (computedFrame),
-				Y = Pos.Bottom (removeButton) + 1,
-				Width = Dim.Fill (),
-				Height = 5
-			};
-			Win.Add (absoluteFrame);
+		var computedFrame = new FrameView ("Computed Layout") {
+			X = 0,
+			Y = Pos.Bottom (removeButton) + 1,
+			Width = Dim.Percent (50),
+			Height = 5
+		};
+		Win.Add (computedFrame);
 
-			// Demonstrates how changing the View.Frame property can move Views
-			var moveBtnA = new Button (0, 0, "Move This Button via Frame") {
-				ColorScheme = Colors.Error,
-			};
-			moveBtnA.Clicked += (s,e) => {
-				moveBtnA.Frame = new Rect (moveBtnA.Frame.X + 5, moveBtnA.Frame.Y, moveBtnA.Frame.Width, moveBtnA.Frame.Height);
-			};
-			absoluteFrame.Add (moveBtnA);
+		// Demonstrates how changing the View.Frame property can move Views
+		var moveBtn = new Button ("Move This \u263b Button _via Pos") {
+			X = 0,
+			Y = Pos.Center () - 1,
+			Width = 30,
+			ColorScheme = Colors.Error,
+		};
+		moveBtn.Clicked += (s, e) => {
+			moveBtn.X = moveBtn.Frame.X + 5;
+			// This is already fixed with the call to SetNeedDisplay() in the Pos Dim.
+			//computedFrame.LayoutSubviews (); // BUGBUG: This call should not be needed. View.X is not causing relayout correctly
+		};
+		computedFrame.Add (moveBtn);
 
-			// Demonstrates how changing the View.Frame property can SIZE Views (#583)
-			var sizeBtnA = new Button (0, 2, " ~  s  gui.cs   master ↑10 = Со_хранить") {
-				ColorScheme = Colors.Error,
-			};
-			sizeBtnA.Clicked += (s,e) => {
-				sizeBtnA.Frame = new Rect (sizeBtnA.Frame.X, sizeBtnA.Frame.Y, sizeBtnA.Frame.Width + 5, sizeBtnA.Frame.Height);
-			};
-			absoluteFrame.Add (sizeBtnA);
+		// Demonstrates how changing the View.Frame property can SIZE Views (#583)
+		var sizeBtn = new Button ("Size This \u263a Button _via Pos") {
+			X = 0,
+			Y = Pos.Center () + 1,
+			Width = 30,
+			ColorScheme = Colors.Error,
+		};
+		sizeBtn.Clicked += (s, e) => {
+			sizeBtn.Width = sizeBtn.Frame.Width + 5;
+			//computedFrame.LayoutSubviews (); // FIXED: This call should not be needed. View.X is not causing relayout correctly
+		};
+		computedFrame.Add (sizeBtn);
 
-			var label = new Label ("Text Alignment (changes the four buttons above): ") {
-				X = 2,
-				Y = Pos.Bottom (computedFrame) + 1,
-			};
-			Win.Add (label);
+		var absoluteFrame = new FrameView ("Absolute Layout") {
+			X = Pos.Right (computedFrame),
+			Y = Pos.Bottom (removeButton) + 1,
+			Width = Dim.Fill (),
+			Height = 5
+		};
+		Win.Add (absoluteFrame);
 
-			var radioGroup = new RadioGroup (new string [] { "Left", "Right", "Centered", "Justified" }) {
-				X = 4,
-				Y = Pos.Bottom (label) + 1,
-				SelectedItem = 2,
-			};
-			Win.Add (radioGroup);
+		// Demonstrates how changing the View.Frame property can move Views
+		var moveBtnA = new Button (0, 0, "Move This Button via Frame") {
+			ColorScheme = Colors.Error,
+		};
+		moveBtnA.Clicked += (s, e) => {
+			moveBtnA.Frame = new Rect (moveBtnA.Frame.X + 5, moveBtnA.Frame.Y, moveBtnA.Frame.Width, moveBtnA.Frame.Height);
+		};
+		absoluteFrame.Add (moveBtnA);
 
-			// Demo changing hotkey
-			string MoveHotkey (string txt)
-			{
-				// Remove the '_'
-				var runes = txt.ToRuneList ();
+		// Demonstrates how changing the View.Frame property can SIZE Views (#583)
+		var sizeBtnA = new Button (0, 2, " ~  s  gui.cs   master ↑10 = Со_хранить") {
+			ColorScheme = Colors.Error,
+		};
+		sizeBtnA.Clicked += (s, e) => {
+			sizeBtnA.Frame = new Rect (sizeBtnA.Frame.X, sizeBtnA.Frame.Y, sizeBtnA.Frame.Width + 5, sizeBtnA.Frame.Height);
+		};
+		absoluteFrame.Add (sizeBtnA);
 
-				var i = runes.IndexOf ((Rune)'_');
-				string start = "";
-				if (i > -1) {
-					start = StringExtensions.ToString (runes.GetRange (0, i));
-				}
-				txt = start + StringExtensions.ToString (runes.GetRange (i + 1, runes.Count - (i + 1)));
+		var label = new Label ("Text Alignment (changes the four buttons above): ") {
+			X = 2,
+			Y = Pos.Bottom (computedFrame) + 1,
+		};
+		Win.Add (label);
 
-				runes = txt.ToRuneList ();
+		var radioGroup = new RadioGroup (new string [] { "Left", "Right", "Centered", "Justified" }) {
+			X = 4,
+			Y = Pos.Bottom (label) + 1,
+			SelectedItem = 2,
+		};
+		Win.Add (radioGroup);
 
-				// Move over one or go to start
-				i++;
-				if (i >= runes.Count) {
-					i = 0;
-				}
+		// Demo changing hotkey
+		string MoveHotkey (string txt)
+		{
+			// Remove the '_'
+			var runes = txt.ToRuneList ();
 
-				// Slip in the '_'
+			var i = runes.IndexOf ((Rune)'_');
+			string start = "";
+			if (i > -1) {
 				start = StringExtensions.ToString (runes.GetRange (0, i));
-				return start + '_' + StringExtensions.ToString (runes.GetRange (i, runes.Count - i));
 			}
+			txt = start + StringExtensions.ToString (runes.GetRange (i + 1, runes.Count - (i + 1)));
 
-			var mhkb = "Click to Change th_is Button's Hotkey";
-			var moveHotKeyBtn = new Button (mhkb) {
-				X = 2,
-				Y = Pos.Bottom (radioGroup) + 1,
-				Width = Dim.Width (computedFrame) - 2,
-				ColorScheme = Colors.TopLevel,
-			};
-			moveHotKeyBtn.Clicked += (s,e) => {
-				moveHotKeyBtn.Text = MoveHotkey (moveHotKeyBtn.Text);
-			};
-			Win.Add (moveHotKeyBtn);
+			runes = txt.ToRuneList ();
 
-			var muhkb = " ~  s  gui.cs   master ↑10 = Сохранить";
-			var moveUnicodeHotKeyBtn = new Button (muhkb) {
-				X = Pos.Left (absoluteFrame) + 1,
-				Y = Pos.Bottom (radioGroup) + 1,
-				Width = Dim.Width (absoluteFrame) - 2, // BUGBUG: Not always the width isn't calculated correctly.
-				ColorScheme = Colors.TopLevel,
-			};
-			moveUnicodeHotKeyBtn.Clicked += (s,e) => {
-				moveUnicodeHotKeyBtn.Text = MoveHotkey (moveUnicodeHotKeyBtn.Text);
-			};
-			Win.Add (moveUnicodeHotKeyBtn);
-
-			radioGroup.SelectedItemChanged += (s, args) => {
-				switch (args.SelectedItem) {
-				case 0:
-					moveBtn.TextAlignment = TextAlignment.Left;
-					sizeBtn.TextAlignment = TextAlignment.Left;
-					moveBtnA.TextAlignment = TextAlignment.Left;
-					sizeBtnA.TextAlignment = TextAlignment.Left;
-					moveHotKeyBtn.TextAlignment = TextAlignment.Left;
-					moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Left;
-					break;
-				case 1:
-					moveBtn.TextAlignment = TextAlignment.Right;
-					sizeBtn.TextAlignment = TextAlignment.Right;
-					moveBtnA.TextAlignment = TextAlignment.Right;
-					sizeBtnA.TextAlignment = TextAlignment.Right;
-					moveHotKeyBtn.TextAlignment = TextAlignment.Right;
-					moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Right;
-					break;
-				case 2:
-					moveBtn.TextAlignment = TextAlignment.Centered;
-					sizeBtn.TextAlignment = TextAlignment.Centered;
-					moveBtnA.TextAlignment = TextAlignment.Centered;
-					sizeBtnA.TextAlignment = TextAlignment.Centered;
-					moveHotKeyBtn.TextAlignment = TextAlignment.Centered;
-					moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Centered;
-					break;
-				case 3:
-					moveBtn.TextAlignment = TextAlignment.Justified;
-					sizeBtn.TextAlignment = TextAlignment.Justified;
-					moveBtnA.TextAlignment = TextAlignment.Justified;
-					sizeBtnA.TextAlignment = TextAlignment.Justified;
-					moveHotKeyBtn.TextAlignment = TextAlignment.Justified;
-					moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Justified;
-					break;
-				}
-			};
+			// Move over one or go to start
+			i++;
+			if (i >= runes.Count) {
+				i = 0;
+			}
 
-			Application.Top.Ready += (s,e) => radioGroup.Refresh ();
+			// Slip in the '_'
+			start = StringExtensions.ToString (runes.GetRange (0, i));
+			return start + '_' + StringExtensions.ToString (runes.GetRange (i, runes.Count - i));
 		}
+
+		var mhkb = "Click to Change th_is Button's Hotkey";
+		var moveHotKeyBtn = new Button (mhkb) {
+			X = 2,
+			Y = Pos.Bottom (radioGroup) + 1,
+			Width = Dim.Width (computedFrame) - 2,
+			ColorScheme = Colors.TopLevel,
+		};
+		moveHotKeyBtn.Clicked += (s, e) => {
+			moveHotKeyBtn.Text = MoveHotkey (moveHotKeyBtn.Text);
+		};
+		Win.Add (moveHotKeyBtn);
+
+		var muhkb = " ~  s  gui.cs   master ↑10 = Сохранить";
+		var moveUnicodeHotKeyBtn = new Button (muhkb) {
+			X = Pos.Left (absoluteFrame) + 1,
+			Y = Pos.Bottom (radioGroup) + 1,
+			Width = Dim.Width (absoluteFrame) - 2, // BUGBUG: Not always the width isn't calculated correctly.
+			ColorScheme = Colors.TopLevel,
+		};
+		moveUnicodeHotKeyBtn.Clicked += (s, e) => {
+			moveUnicodeHotKeyBtn.Text = MoveHotkey (moveUnicodeHotKeyBtn.Text);
+		};
+		Win.Add (moveUnicodeHotKeyBtn);
+
+		radioGroup.SelectedItemChanged += (s, args) => {
+			switch (args.SelectedItem) {
+			case 0:
+				moveBtn.TextAlignment = TextAlignment.Left;
+				sizeBtn.TextAlignment = TextAlignment.Left;
+				moveBtnA.TextAlignment = TextAlignment.Left;
+				sizeBtnA.TextAlignment = TextAlignment.Left;
+				moveHotKeyBtn.TextAlignment = TextAlignment.Left;
+				moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Left;
+				break;
+			case 1:
+				moveBtn.TextAlignment = TextAlignment.Right;
+				sizeBtn.TextAlignment = TextAlignment.Right;
+				moveBtnA.TextAlignment = TextAlignment.Right;
+				sizeBtnA.TextAlignment = TextAlignment.Right;
+				moveHotKeyBtn.TextAlignment = TextAlignment.Right;
+				moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Right;
+				break;
+			case 2:
+				moveBtn.TextAlignment = TextAlignment.Centered;
+				sizeBtn.TextAlignment = TextAlignment.Centered;
+				moveBtnA.TextAlignment = TextAlignment.Centered;
+				sizeBtnA.TextAlignment = TextAlignment.Centered;
+				moveHotKeyBtn.TextAlignment = TextAlignment.Centered;
+				moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Centered;
+				break;
+			case 3:
+				moveBtn.TextAlignment = TextAlignment.Justified;
+				sizeBtn.TextAlignment = TextAlignment.Justified;
+				moveBtnA.TextAlignment = TextAlignment.Justified;
+				sizeBtnA.TextAlignment = TextAlignment.Justified;
+				moveHotKeyBtn.TextAlignment = TextAlignment.Justified;
+				moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Justified;
+				break;
+			}
+		};
+
+		Application.Top.Ready += (s, e) => radioGroup.Refresh ();
 	}
-}
+}

+ 113 - 112
UICatalog/Scenarios/CharacterMap.cs

@@ -23,7 +23,7 @@ namespace UICatalog.Scenarios;
 ///   - Helps test unicode character rendering in Terminal.Gui
 ///   - Illustrates how to use ScrollView to do infinite scrolling
 /// </summary>
-[ScenarioMetadata (Name: "Character Map", Description: "Unicode viewer demonstrating the ScrollView control.")]
+[ScenarioMetadata ("Character Map", "Unicode viewer demonstrating the ScrollView control.")]
 [ScenarioCategory ("Text and Formatting")]
 [ScenarioCategory ("Controls")]
 [ScenarioCategory ("ScrollView")]
@@ -48,9 +48,15 @@ public class CharacterMap : Scenario {
 		};
 		Application.Top.Add (_charMap);
 
-		var jumpLabel = new Label ("Jump To Code Point:") { X = Pos.Right (_charMap) + 1, Y = Pos.Y (_charMap) };
+		var jumpLabel = new Label ("_Jump To Code Point:") {
+			X = Pos.Right (_charMap) + 1,
+			Y = Pos.Y (_charMap),
+			HotKeySpecifier = (Rune)'_'
+		};
 		Application.Top.Add (jumpLabel);
-		var jumpEdit = new TextField () { X = Pos.Right (jumpLabel) + 1, Y = Pos.Y (_charMap), Width = 10, Caption = "e.g. 01BE3" };
+		var jumpEdit = new TextField () {
+			X = Pos.Right (jumpLabel) + 1, Y = Pos.Y (_charMap), Width = 10, Caption = "e.g. 01BE3"
+		};
 		Application.Top.Add (jumpEdit);
 		_errorLabel = new Label ("err") { X = Pos.Right (jumpEdit) + 1, Y = Pos.Y (_charMap), ColorScheme = Colors.ColorSchemes ["error"] };
 		Application.Top.Add (_errorLabel);
@@ -72,7 +78,7 @@ public class CharacterMap : Scenario {
 		//jumpList.Style.ShowVerticalHeaderLines = false;
 		_categoryList.Style.AlwaysShowHeaders = true;
 
-		var isDescending = false;
+		bool isDescending = false;
 
 		_categoryList.Table = CreateCategoryTable (0, isDescending);
 
@@ -81,19 +87,19 @@ public class CharacterMap : Scenario {
 			_categoryList.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out int? clickedCol);
 			if (clickedCol != null && e.MouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)) {
 				var table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
-				var prevSelection = table.Data.ElementAt (_categoryList.SelectedRow).Category;
+				string prevSelection = table.Data.ElementAt (_categoryList.SelectedRow).Category;
 				isDescending = !isDescending;
 
 				_categoryList.Table = CreateCategoryTable (clickedCol.Value, isDescending);
 
 				table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
 				_categoryList.SelectedRow = table.Data
-					.Select ((item, index) => new { item, index })
-					.FirstOrDefault (x => x.item.Category == prevSelection)?.index ?? -1;
+								.Select ((item, index) => new { item, index })
+								.FirstOrDefault (x => x.item.Category == prevSelection)?.index ?? -1;
 			}
 		};
 
-		var longestName = UnicodeRange.Ranges.Max (r => r.Category.GetColumns ());
+		int longestName = UnicodeRange.Ranges.Max (r => r.Category.GetColumns ());
 		_categoryList.Style.ColumnStyles.Add (0, new ColumnStyle () { MaxWidth = longestName, MinWidth = longestName, MinAcceptableWidth = longestName });
 		_categoryList.Style.ColumnStyles.Add (1, new ColumnStyle () { MaxWidth = 1, MinWidth = 6 });
 		_categoryList.Style.ColumnStyles.Add (2, new ColumnStyle () { MaxWidth = 1, MinWidth = 6 });
@@ -101,7 +107,7 @@ public class CharacterMap : Scenario {
 		_categoryList.Width = _categoryList.Style.ColumnStyles.Sum (c => c.Value.MinWidth) + 4;
 
 		_categoryList.SelectedCellChanged += (s, args) => {
-			EnumerableTableSource<UnicodeRange> table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
+			var table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
 			_charMap.StartCodePoint = table.Data.ToArray () [args.NewRow].Start;
 		};
 
@@ -114,11 +120,11 @@ public class CharacterMap : Scenario {
 		_charMap.Width = Dim.Fill () - _categoryList.Width;
 
 		var menu = new MenuBar (new MenuBarItem [] {
-			new MenuBarItem ("_File", new MenuItem [] {
-				new MenuItem ("_Quit", $"{Application.QuitKey}", () => Application.RequestStop ()),
+			new ("_File", new MenuItem [] {
+				new ("_Quit", $"{Application.QuitKey}", () => Application.RequestStop ())
 			}),
-			new MenuBarItem ("_Options", new MenuItem [] {
-				CreateMenuShowWidth (),
+			new ("_Options", new MenuItem [] {
+				CreateMenuShowWidth ()
 			})
 		});
 		Application.Top.Add (menu);
@@ -131,7 +137,7 @@ public class CharacterMap : Scenario {
 	MenuItem CreateMenuShowWidth ()
 	{
 		var item = new MenuItem {
-			Title = "_Show Glyph Width",
+			Title = "_Show Glyph Width"
 		};
 		item.CheckType |= MenuItemCheckStyle.Checked;
 		item.Checked = _charMap?.ShowGlyphWidths;
@@ -145,11 +151,11 @@ public class CharacterMap : Scenario {
 	EnumerableTableSource<UnicodeRange> CreateCategoryTable (int sortByColumn, bool descending)
 	{
 		Func<UnicodeRange, object> orderBy;
-		var categorySort = string.Empty;
-		var startSort = string.Empty;
-		var endSort = string.Empty;
+		string categorySort = string.Empty;
+		string startSort = string.Empty;
+		string endSort = string.Empty;
 
-		var sortIndicator = descending ? CM.Glyphs.DownArrow.ToString () : CM.Glyphs.UpArrow.ToString ();
+		string sortIndicator = descending ? CM.Glyphs.DownArrow.ToString () : CM.Glyphs.UpArrow.ToString ();
 		switch (sortByColumn) {
 		case 0:
 			orderBy = r => r.Category;
@@ -167,19 +173,18 @@ public class CharacterMap : Scenario {
 			throw new ArgumentException ("Invalid column number.");
 		}
 
-		IOrderedEnumerable<UnicodeRange> sortedRanges = descending ?
+		var sortedRanges = descending ?
 			UnicodeRange.Ranges.OrderByDescending (orderBy) :
 			UnicodeRange.Ranges.OrderBy (orderBy);
 
-		return new EnumerableTableSource<UnicodeRange> (sortedRanges, new Dictionary<string, Func<UnicodeRange, object>> ()
-		{
+		return new EnumerableTableSource<UnicodeRange> (sortedRanges, new Dictionary<string, Func<UnicodeRange, object>> () {
 			{ $"Category{categorySort}", s => s.Category },
 			{ $"Start{startSort}", s => $"{s.Start:x5}" },
-			{ $"End{endSort}", s => $"{s.End:x5}" },
+			{ $"End{endSort}", s => $"{s.End:x5}" }
 		});
 	}
 
-	private void JumpEdit_TextChanged (object sender, TextChangedEventArgs e)
+	void JumpEdit_TextChanged (object sender, TextChangedEventArgs e)
 	{
 		var jumpEdit = sender as TextField;
 		if (jumpEdit.Text.Length == 0) {
@@ -217,8 +222,8 @@ public class CharacterMap : Scenario {
 
 		var table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
 		_categoryList.SelectedRow = table.Data
-			.Select ((item, index) => new { item, index })
-			.FirstOrDefault (x => x.item.Start <= result && x.item.End >= result)?.index ?? -1;
+						.Select ((item, index) => new { item, index })
+						.FirstOrDefault (x => x.item.Start <= result && x.item.End >= result)?.index ?? -1;
 		_categoryList.EnsureSelectedCellIsVisible ();
 
 		// Ensure the typed glyph is selected 
@@ -227,7 +232,6 @@ public class CharacterMap : Scenario {
 }
 
 class CharMap : ScrollView {
-
 	/// <summary>
 	/// Specifies the starting offset for the character map. The default is 0x2500 
 	/// which is the Box Drawing characters.
@@ -251,10 +255,10 @@ class CharMap : ScrollView {
 		get => _selected;
 		set {
 			_selected = value;
-			var row = (SelectedCodePoint / 16 * _rowHeight);
-			var col = (SelectedCodePoint % 16 * COLUMN_WIDTH);
+			int row = SelectedCodePoint / 16 * _rowHeight;
+			int col = SelectedCodePoint % 16 * COLUMN_WIDTH;
 
-			var height = (Bounds.Height) - (ShowHorizontalScrollIndicator ? 2 : 1);
+			int height = Bounds.Height - (ShowHorizontalScrollIndicator ? 2 : 1);
 			if (row + ContentOffset.Y < 0) {
 				// Moving up.
 				ContentOffset = new Point (ContentOffset.X, row);
@@ -262,7 +266,7 @@ class CharMap : ScrollView {
 				// Moving down.
 				ContentOffset = new Point (ContentOffset.X, Math.Min (row, row - height + _rowHeight));
 			}
-			var width = (Bounds.Width / COLUMN_WIDTH * COLUMN_WIDTH) - (ShowVerticalScrollIndicator ? RowLabelWidth + 1 : RowLabelWidth);
+			int width = Bounds.Width / COLUMN_WIDTH * COLUMN_WIDTH - (ShowVerticalScrollIndicator ? RowLabelWidth + 1 : RowLabelWidth);
 			if (col + ContentOffset.X < 0) {
 				// Moving left.
 				ContentOffset = new Point (col, ContentOffset.Y);
@@ -282,8 +286,8 @@ class CharMap : ScrollView {
 	/// </summary>
 	public Point Cursor {
 		get {
-			var row = (SelectedCodePoint / 16 * _rowHeight) + ContentOffset.Y + 1;
-			var col = (SelectedCodePoint % 16 * COLUMN_WIDTH) + ContentOffset.X + RowLabelWidth + 1; // + 1 for padding
+			int row = SelectedCodePoint / 16 * _rowHeight + ContentOffset.Y + 1;
+			int col = SelectedCodePoint % 16 * COLUMN_WIDTH + ContentOffset.X + RowLabelWidth + 1; // + 1 for padding
 			return new Point (col, row);
 		}
 		set => throw new NotImplementedException ();
@@ -292,10 +296,10 @@ class CharMap : ScrollView {
 	public override void PositionCursor ()
 	{
 		if (HasFocus &&
-			Cursor.X >= RowLabelWidth &&
-			Cursor.X < Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0) &&
-			Cursor.Y > 0 &&
-			Cursor.Y < Bounds.Height - (ShowHorizontalScrollIndicator ? 1 : 0)) {
+		Cursor.X >= RowLabelWidth &&
+		Cursor.X < Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0) &&
+		Cursor.Y > 0 &&
+		Cursor.Y < Bounds.Height - (ShowHorizontalScrollIndicator ? 1 : 0)) {
 			Driver.SetCursorVisibility (CursorVisibility.Default);
 			Move (Cursor.X, Cursor.Y);
 		} else {
@@ -320,13 +324,14 @@ class CharMap : ScrollView {
 	public static int MaxCodePoint => 0x10FFFF;
 
 	static int RowLabelWidth => $"U+{MaxCodePoint:x5}".Length + 1;
-	static int RowWidth => RowLabelWidth + (COLUMN_WIDTH * 16);
+
+	static int RowWidth => RowLabelWidth + COLUMN_WIDTH * 16;
 
 	public CharMap ()
 	{
 		ColorScheme = Colors.Dialog;
 		CanFocus = true;
-		ContentSize = new Size (CharMap.RowWidth, (int)((MaxCodePoint / 16 + (ShowHorizontalScrollIndicator ? 2 : 1)) * _rowHeight));
+		ContentSize = new Size (RowWidth, (int)((MaxCodePoint / 16 + (ShowHorizontalScrollIndicator ? 2 : 1)) * _rowHeight));
 
 		AddCommand (Command.ScrollUp, () => {
 			if (SelectedCodePoint >= 16) {
@@ -353,12 +358,12 @@ class CharMap : ScrollView {
 			return true;
 		});
 		AddCommand (Command.PageUp, () => {
-			var page = (Bounds.Height / _rowHeight - 1) * 16;
+			int page = (Bounds.Height / _rowHeight - 1) * 16;
 			SelectedCodePoint -= Math.Min (page, SelectedCodePoint);
 			return true;
 		});
 		AddCommand (Command.PageDown, () => {
-			var page = (Bounds.Height / _rowHeight - 1) * 16;
+			int page = (Bounds.Height / _rowHeight - 1) * 16;
 			SelectedCodePoint += Math.Min (page, MaxCodePoint - SelectedCodePoint);
 			return true;
 		});
@@ -370,7 +375,7 @@ class CharMap : ScrollView {
 			SelectedCodePoint = MaxCodePoint;
 			return true;
 		});
-		AddKeyBinding (Key.Enter, Command.Accept);
+		KeyBindings.Add (Key.Enter, Command.Accept);
 		AddCommand (Command.Accept, () => {
 			ShowDetails ();
 			return true;
@@ -379,11 +384,10 @@ class CharMap : ScrollView {
 		MouseClick += Handle_MouseClick;
 	}
 
-	private void CopyCodePoint () => Clipboard.Contents = $"U+{SelectedCodePoint:x5}";
-	private void CopyGlyph () => Clipboard.Contents = $"{new Rune (SelectedCodePoint)}";
+	void CopyCodePoint () => Clipboard.Contents = $"U+{SelectedCodePoint:x5}";
+	void CopyGlyph () => Clipboard.Contents = $"{new Rune (SelectedCodePoint)}";
 
-	public override void OnDrawContent (Rect contentArea)
-	{
+	public override void OnDrawContent (Rect contentArea) =>
 		//if (ShowHorizontalScrollIndicator && ContentSize.Height < (int)(MaxCodePoint / 16 + 2)) {
 		//	//ContentSize = new Size (CharMap.RowWidth, (int)(MaxCodePoint / 16 + 2));
 		//	//ContentSize = new Size (CharMap.RowWidth, (int)(MaxCodePoint / 16) * _rowHeight + 2);
@@ -404,7 +408,6 @@ class CharMap : ScrollView {
 		//	ContentOffset = new Point (0, ContentOffset.Y < -ContentSize.Height + Bounds.Height ? ContentOffset.Y - 1 : ContentOffset.Y);
 		//}
 		base.OnDrawContent (contentArea);
-	}
 
 	//public void CharMap_DrawContent (object s, DrawEventArgs a)
 	public override void OnDrawContentComplete (Rect contentArea)
@@ -412,7 +415,7 @@ class CharMap : ScrollView {
 		if (contentArea.Height == 0 || contentArea.Width == 0) {
 			return;
 		}
-		Rect viewport = new Rect (ContentOffset,
+		var viewport = new Rect (ContentOffset,
 			new Size (Math.Max (Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0), 0),
 				Math.Max (Bounds.Height - (ShowHorizontalScrollIndicator ? 1 : 0), 0)));
 
@@ -426,31 +429,31 @@ class CharMap : ScrollView {
 			Driver.Clip = new Rect (Driver.Clip.Location, new Size (Driver.Clip.Width - 1, Driver.Clip.Height));
 		}
 
-		var cursorCol = Cursor.X - ContentOffset.X - RowLabelWidth - 1;
-		var cursorRow = Cursor.Y - ContentOffset.Y - 1;
+		int cursorCol = Cursor.X - ContentOffset.X - RowLabelWidth - 1;
+		int cursorRow = Cursor.Y - ContentOffset.Y - 1;
 
 		Driver.SetAttribute (GetHotNormalColor ());
 		Move (0, 0);
 		Driver.AddStr (new string (' ', RowLabelWidth + 1));
 		for (int hexDigit = 0; hexDigit < 16; hexDigit++) {
-			var x = ContentOffset.X + RowLabelWidth + (hexDigit * COLUMN_WIDTH);
+			int x = ContentOffset.X + RowLabelWidth + hexDigit * COLUMN_WIDTH;
 			if (x > RowLabelWidth - 2) {
 				Move (x, 0);
 				Driver.SetAttribute (GetHotNormalColor ());
 				Driver.AddStr (" ");
-				Driver.SetAttribute (HasFocus && (cursorCol + ContentOffset.X + RowLabelWidth == x) ? ColorScheme.HotFocus : GetHotNormalColor ());
+				Driver.SetAttribute (HasFocus && cursorCol + ContentOffset.X + RowLabelWidth == x ? ColorScheme.HotFocus : GetHotNormalColor ());
 				Driver.AddStr ($"{hexDigit:x}");
 				Driver.SetAttribute (GetHotNormalColor ());
 				Driver.AddStr (" ");
 			}
 		}
 
-		var firstColumnX = viewport.X + RowLabelWidth;
+		int firstColumnX = viewport.X + RowLabelWidth;
 		for (int y = 1; y < Bounds.Height; y++) {
 			// What row is this?
-			var row = (y - ContentOffset.Y - 1) / _rowHeight;
+			int row = (y - ContentOffset.Y - 1) / _rowHeight;
 
-			var val = (row) * 16;
+			int val = row * 16;
 			if (val > MaxCodePoint) {
 				continue;
 			}
@@ -458,18 +461,18 @@ class CharMap : ScrollView {
 			Driver.SetAttribute (GetNormalColor ());
 			for (int col = 0; col < 16; col++) {
 
-				var x = firstColumnX + COLUMN_WIDTH * col + 1;
+				int x = firstColumnX + COLUMN_WIDTH * col + 1;
 
 				Move (x, y);
 				if (cursorRow + ContentOffset.Y + 1 == y && cursorCol + ContentOffset.X + firstColumnX + 1 == x && !HasFocus) {
 					Driver.SetAttribute (GetFocusColor ());
 				}
-				var scalar = val + col;
-				Rune rune = (Rune)'?';
+				int scalar = val + col;
+				var rune = (Rune)'?';
 				if (Rune.IsValid (scalar)) {
 					rune = new Rune (scalar);
 				}
-				var width = rune.GetColumns ();
+				int width = rune.GetColumns ();
 
 				// are we at first row of the row?
 				if (!ShowGlyphWidths || (y - ContentOffset.Y) % _rowHeight > 0) {
@@ -487,7 +490,7 @@ class CharMap : ScrollView {
 							sb.Append (rune);
 							// Try normalizing after combining with 'a'. If it normalizes, at least 
 							// it'll show on the 'a'. If not, just show the replacement char.
-							var normal = sb.ToString ().Normalize (NormalizationForm.FormC);
+							string normal = sb.ToString ().Normalize (NormalizationForm.FormC);
 							if (normal.Length == 1) {
 								Driver.AddRune (normal [0]);
 							} else {
@@ -505,7 +508,7 @@ class CharMap : ScrollView {
 				}
 			}
 			Move (0, y);
-			Driver.SetAttribute (HasFocus && (cursorRow + ContentOffset.Y + 1 == y) ? ColorScheme.HotFocus : ColorScheme.HotNormal);
+			Driver.SetAttribute (HasFocus && cursorRow + ContentOffset.Y + 1 == y ? ColorScheme.HotFocus : ColorScheme.HotNormal);
 			if (!ShowGlyphWidths || (y - ContentOffset.Y) % _rowHeight > 0) {
 				Driver.AddStr ($"U+{val / 16:x5}_ ");
 			} else {
@@ -515,12 +518,13 @@ class CharMap : ScrollView {
 		Driver.Clip = oldClip;
 	}
 
-	ContextMenu _contextMenu = new ContextMenu ();
+	ContextMenu _contextMenu = new ();
+
 	void Handle_MouseClick (object sender, MouseEventEventArgs args)
 	{
 		var me = args.MouseEvent;
 		if (me.Flags != MouseFlags.ReportMousePosition && me.Flags != MouseFlags.Button1Clicked &&
-			me.Flags != MouseFlags.Button1DoubleClicked) {
+		me.Flags != MouseFlags.Button1DoubleClicked) {
 			return;
 		}
 
@@ -528,21 +532,20 @@ class CharMap : ScrollView {
 			me.Y = Cursor.Y;
 		}
 
-		if (me.Y > 0) {
-		}
+		if (me.Y > 0) { }
 
-		if (me.X < RowLabelWidth || me.X > RowLabelWidth + (16 * COLUMN_WIDTH) - 1) {
+		if (me.X < RowLabelWidth || me.X > RowLabelWidth + 16 * COLUMN_WIDTH - 1) {
 			me.X = Cursor.X;
 		}
 
-		var row = (me.Y - 1 - ContentOffset.Y) / _rowHeight; // -1 for header
-		var col = (me.X - RowLabelWidth - ContentOffset.X) / COLUMN_WIDTH;
+		int row = (me.Y - 1 - ContentOffset.Y) / _rowHeight; // -1 for header
+		int col = (me.X - RowLabelWidth - ContentOffset.X) / COLUMN_WIDTH;
 
 		if (col > 15) {
 			col = 15;
 		}
 
-		var val = (row) * 16 + col;
+		int val = row * 16 + col;
 		if (val > MaxCodePoint) {
 			return;
 		}
@@ -566,11 +569,9 @@ class CharMap : ScrollView {
 			SelectedCodePoint = val;
 			_contextMenu = new ContextMenu (me.X + 1, me.Y + 1,
 				new MenuBarItem (new MenuItem [] {
-					new MenuItem ("_Copy Glyph", "", () => CopyGlyph (), null, null, Key.C | Key.CtrlMask),
-					new MenuItem ("Copy Code _Point", "", () => CopyCodePoint (), null, null, Key.C | Key.ShiftMask | Key.CtrlMask),
-				}) {
-
-				}
+					new ("_Copy Glyph", "", () => CopyGlyph (), null, null, (KeyCode)Key.C.WithCtrl),
+					new ("Copy Code _Point", "", () => CopyCodePoint (), null, null, (KeyCode)Key.C.WithCtrl.WithShift)
+				}) { }
 			);
 			_contextMenu.Show ();
 		}
@@ -582,7 +583,7 @@ class CharMap : ScrollView {
 			return str;
 		}
 
-		TextInfo textInfo = new CultureInfo ("en-US", false).TextInfo;
+		var textInfo = new CultureInfo ("en-US", false).TextInfo;
 
 		str = textInfo.ToLower (str);
 		str = textInfo.ToTitleCase (str);
@@ -614,7 +615,7 @@ class CharMap : ScrollView {
 		var spinner = new SpinnerView () {
 			X = Pos.Center (),
 			Y = Pos.Center (),
-			Style = new SpinnerStyle.Aesthetic (),
+			Style = new Aesthetic ()
 
 		};
 		spinner.AutoSpin = true;
@@ -640,11 +641,11 @@ class CharMap : ScrollView {
 		if (!string.IsNullOrEmpty (decResponse)) {
 			string name = string.Empty;
 
-			using (JsonDocument document = JsonDocument.Parse (decResponse)) {
-				JsonElement root = document.RootElement;
+			using (var document = JsonDocument.Parse (decResponse)) {
+				var root = document.RootElement;
 
 				// Get a property by name and output its value
-				if (root.TryGetProperty ("name", out JsonElement nameElement)) {
+				if (root.TryGetProperty ("name", out var nameElement)) {
 					name = nameElement.GetString ();
 				}
 
@@ -654,12 +655,12 @@ class CharMap : ScrollView {
 				//	Console.WriteLine (nestedPropertyElement.GetString ());
 				//}
 				decResponse = JsonSerializer.Serialize (document.RootElement, new
-						JsonSerializerOptions {
-					WriteIndented = true
-				});
+					JsonSerializerOptions {
+						WriteIndented = true
+					});
 			}
 
-			var title = $"{ToCamelCase (name)} - {new Rune (SelectedCodePoint)} U+{SelectedCodePoint:x5}";
+			string title = $"{ToCamelCase (name)} - {new Rune (SelectedCodePoint)} U+{SelectedCodePoint:x5}";
 
 			var copyGlyph = new Button ("Copy _Glyph");
 			var copyCP = new Button ("Copy Code _Point");
@@ -819,7 +820,7 @@ class CharMap : ScrollView {
 }
 
 public class UcdApiClient {
-	private static readonly HttpClient httpClient = new HttpClient ();
+	static readonly HttpClient httpClient = new ();
 	public const string BaseUrl = "https://ucdapi.org/unicode/latest/";
 
 	public async Task<string> GetCodepointHex (string hex)
@@ -851,49 +852,49 @@ public class UcdApiClient {
 	}
 }
 
-
 class UnicodeRange {
 	public int Start;
 	public int End;
 	public string Category;
+
 	public UnicodeRange (int start, int end, string category)
 	{
-		this.Start = start;
-		this.End = end;
-		this.Category = category;
+		Start = start;
+		End = end;
+		Category = category;
 	}
 
 	public static List<UnicodeRange> GetRanges ()
 	{
-		var ranges = (from r in typeof (UnicodeRanges).GetProperties (System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)
-			      let urange = r.GetValue (null) as System.Text.Unicode.UnicodeRange
-			      let name = string.IsNullOrEmpty (r.Name) ? $"U+{urange.FirstCodePoint:x5}-U+{urange.FirstCodePoint + urange.Length:x5}" : r.Name
-			      where name != "None" && name != "All"
-			      select new UnicodeRange (urange.FirstCodePoint, urange.FirstCodePoint + urange.Length, name));
+		var ranges = from r in typeof (UnicodeRanges).GetProperties (System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)
+				let urange = r.GetValue (null) as System.Text.Unicode.UnicodeRange
+				let name = string.IsNullOrEmpty (r.Name) ? $"U+{urange.FirstCodePoint:x5}-U+{urange.FirstCodePoint + urange.Length:x5}" : r.Name
+				where name != "None" && name != "All"
+				select new UnicodeRange (urange.FirstCodePoint, urange.FirstCodePoint + urange.Length, name);
 
 		// .NET 8.0 only supports BMP in UnicodeRanges: https://learn.microsoft.com/en-us/dotnet/api/system.text.unicode.unicoderanges?view=net-8.0
 		var nonBmpRanges = new List<UnicodeRange> {
 
-			new UnicodeRange (0x1F130, 0x1F149   ,"Squared Latin Capital Letters"),
-			new UnicodeRange (0x12400, 0x1240f   ,"Cuneiform Numbers and Punctuation"),
-			new UnicodeRange (0x10000, 0x1007F   ,"Linear B Syllabary"),
-			new UnicodeRange (0x10080, 0x100FF   ,"Linear B Ideograms"),
-			new UnicodeRange (0x10100, 0x1013F   ,"Aegean Numbers"),
-			new UnicodeRange (0x10300, 0x1032F   ,"Old Italic"),
-			new UnicodeRange (0x10330, 0x1034F   ,"Gothic"),
-			new UnicodeRange (0x10380, 0x1039F   ,"Ugaritic"),
-			new UnicodeRange (0x10400, 0x1044F   ,"Deseret"),
-			new UnicodeRange (0x10450, 0x1047F   ,"Shavian"),
-			new UnicodeRange (0x10480, 0x104AF   ,"Osmanya"),
-			new UnicodeRange (0x10800, 0x1083F   ,"Cypriot Syllabary"),
-			new UnicodeRange (0x1D000, 0x1D0FF   ,"Byzantine Musical Symbols"),
-			new UnicodeRange (0x1D100, 0x1D1FF   ,"Musical Symbols"),
-			new UnicodeRange (0x1D300, 0x1D35F   ,"Tai Xuan Jing Symbols"),
-			new UnicodeRange (0x1D400, 0x1D7FF   ,"Mathematical Alphanumeric Symbols"),
-			new UnicodeRange (0x1F600, 0x1F532   ,"Emojis Symbols"),
-			new UnicodeRange (0x20000, 0x2A6DF   ,"CJK Unified Ideographs Extension B"),
-			new UnicodeRange (0x2F800, 0x2FA1F   ,"CJK Compatibility Ideographs Supplement"),
-			new UnicodeRange (0xE0000, 0xE007F   ,"Tags"),
+			new (0x1F130, 0x1F149, "Squared Latin Capital Letters"),
+			new (0x12400, 0x1240f, "Cuneiform Numbers and Punctuation"),
+			new (0x10000, 0x1007F, "Linear B Syllabary"),
+			new (0x10080, 0x100FF, "Linear B Ideograms"),
+			new (0x10100, 0x1013F, "Aegean Numbers"),
+			new (0x10300, 0x1032F, "Old Italic"),
+			new (0x10330, 0x1034F, "Gothic"),
+			new (0x10380, 0x1039F, "Ugaritic"),
+			new (0x10400, 0x1044F, "Deseret"),
+			new (0x10450, 0x1047F, "Shavian"),
+			new (0x10480, 0x104AF, "Osmanya"),
+			new (0x10800, 0x1083F, "Cypriot Syllabary"),
+			new (0x1D000, 0x1D0FF, "Byzantine Musical Symbols"),
+			new (0x1D100, 0x1D1FF, "Musical Symbols"),
+			new (0x1D300, 0x1D35F, "Tai Xuan Jing Symbols"),
+			new (0x1D400, 0x1D7FF, "Mathematical Alphanumeric Symbols"),
+			new (0x1F600, 0x1F532, "Emojis Symbols"),
+			new (0x20000, 0x2A6DF, "CJK Unified Ideographs Extension B"),
+			new (0x2F800, 0x2FA1F, "CJK Compatibility Ideographs Supplement"),
+			new (0xE0000, 0xE007F, "Tags")
 		};
 
 		return ranges.Concat (nonBmpRanges).OrderBy (r => r.Category).ToList ();

+ 1 - 1
UICatalog/Scenarios/CollectionNavigatorTester.cs

@@ -95,7 +95,7 @@ namespace UICatalog.Scenarios {
 					allowMarking,
 					allowMultiSelection,
 					null,
-					new MenuItem ("_Quit", $"{Application.QuitKey}", () => Quit(), null, null, Application.QuitKey),
+					new MenuItem ("_Quit", $"{Application.QuitKey}", () => Quit(), null, null, (KeyCode)Application.QuitKey),
 				}),
 				new MenuBarItem("_Quit", $"{Application.QuitKey}", () => Quit()),
 			});

+ 3 - 3
UICatalog/Scenarios/ConfigurationEditor.cs

@@ -49,11 +49,11 @@ namespace UICatalog.Scenarios {
 
 			Application.Top.Add (_tileView);
 
-			_lenStatusItem = new StatusItem (Key.CharMask, "Len: ", null);
+			_lenStatusItem = new StatusItem (KeyCode.CharMask, "Len: ", null);
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Application.QuitKey, $"{Application.QuitKey} Quit", () => Quit()),
-				new StatusItem(Key.F5, "~F5~ Reload", () => Reload()),
-				new StatusItem(Key.CtrlMask | Key.S, "~^S~ Save", () => Save()),
+				new StatusItem(KeyCode.F5, "~F5~ Reload", () => Reload()),
+				new StatusItem(KeyCode.CtrlMask | KeyCode.S, "~^S~ Save", () => Save()),
 				_lenStatusItem,
 			});
 

+ 132 - 126
UICatalog/Scenarios/ContextMenus.cs

@@ -3,97 +3,104 @@ using System.Globalization;
 using System.Threading;
 using Terminal.Gui;
 
-namespace UICatalog.Scenarios {
-	[ScenarioMetadata (Name: "ContextMenus", Description: "Context Menu Sample.")]
-	[ScenarioCategory ("Menus")]
-	public class ContextMenus : Scenario {
-		private ContextMenu _contextMenu = new ContextMenu ();
-		private readonly List<CultureInfo> _cultureInfos = Application.SupportedCultures;
-		private MenuItem _miForceMinimumPosToZero;
-		private bool _forceMinimumPosToZero = true;
-		private TextField _tfTopLeft, _tfTopRight, _tfMiddle, _tfBottomLeft, _tfBottomRight;
-		private MenuItem _miUseSubMenusSingleFrame;
-		private bool _useSubMenusSingleFrame;
-
-		public override void Setup ()
-		{
-			var text = "Context Menu";
-			var width = 20;
-
-			Win.Add (new Label ("Press 'Ctrl + Space' to open the Window context menu.") {
-				X = Pos.Center (),
-				Y = 1
-			});
-
-			_tfTopLeft = new TextField (text) {
-				Width = width
-			};
-			Win.Add (_tfTopLeft);
-
-			_tfTopRight = new TextField (text) {
-				X = Pos.AnchorEnd (width),
-				Width = width
-			};
-			Win.Add (_tfTopRight);
-
-			_tfMiddle = new TextField (text) {
-				X = Pos.Center (),
-				Y = Pos.Center (),
-				Width = width
-			};
-			Win.Add (_tfMiddle);
-
-			_tfBottomLeft = new TextField (text) {
-				Y = Pos.AnchorEnd (1),
-				Width = width
-			};
-			Win.Add (_tfBottomLeft);
-
-			_tfBottomRight = new TextField (text) {
-				X = Pos.AnchorEnd (width),
-				Y = Pos.AnchorEnd (1),
-				Width = width
-			};
-			Win.Add (_tfBottomRight);
-
-			Point mousePos = default;
-
-			Win.KeyPressed += (s, e) => {
-				if (e.KeyEvent.Key == (Key.Space | Key.CtrlMask)) {
-					ShowContextMenu (mousePos.X, mousePos.Y);
-					e.Handled = true;
-				}
-			};
-
-			Win.MouseClick += (s, e) => {
-				if (e.MouseEvent.Flags == _contextMenu.MouseFlags) {
-					ShowContextMenu (e.MouseEvent.X, e.MouseEvent.Y);
-					e.Handled = true;
-				}
-			};
-
-			Application.MouseEvent += ApplicationMouseEvent;
+namespace UICatalog.Scenarios;
+[ScenarioMetadata (Name: "ContextMenus", Description: "Context Menu Sample.")]
+[ScenarioCategory ("Menus")]
+public class ContextMenus : Scenario {
+	private ContextMenu _contextMenu = new ContextMenu ();
+	private readonly List<CultureInfo> _cultureInfos = Application.SupportedCultures;
+	private MenuItem _miForceMinimumPosToZero;
+	private bool _forceMinimumPosToZero = true;
+	private TextField _tfTopLeft, _tfTopRight, _tfMiddle, _tfBottomLeft, _tfBottomRight;
+	private MenuItem _miUseSubMenusSingleFrame;
+	private bool _useSubMenusSingleFrame;
+
+	public override void Setup ()
+	{
+		var text = "Context Menu";
+		var width = 20;
+		KeyCode winContextMenuKey = KeyCode.Space | KeyCode.CtrlMask;
+
+		var label = new Label ($"Press '{winContextMenuKey}' to open the Window context menu.") {
+			X = Pos.Center (),
+			Y = 1
+		};
+		Win.Add (label);
+		label = new Label ($"Press '{ContextMenu.DefaultKey}' to open the TextField context menu.") {
+			X = Pos.Center (),
+			Y = Pos.Bottom (label)
+		};
+		Win.Add (label);
+
+		_tfTopLeft = new TextField (text) {
+			Width = width
+		};
+		Win.Add (_tfTopLeft);
+
+		_tfTopRight = new TextField (text) {
+			X = Pos.AnchorEnd (width),
+			Width = width
+		};
+		Win.Add (_tfTopRight);
+
+		_tfMiddle = new TextField (text) {
+			X = Pos.Center (),
+			Y = Pos.Center (),
+			Width = width
+		};
+		Win.Add (_tfMiddle);
+
+		_tfBottomLeft = new TextField (text) {
+			Y = Pos.AnchorEnd (1),
+			Width = width
+		};
+		Win.Add (_tfBottomLeft);
+
+		_tfBottomRight = new TextField (text) {
+			X = Pos.AnchorEnd (width),
+			Y = Pos.AnchorEnd (1),
+			Width = width
+		};
+		Win.Add (_tfBottomRight);
+
+		Point mousePos = default;
+
+		Win.KeyDown += (s, e) => {
+			if (e.KeyCode == winContextMenuKey) {
+				ShowContextMenu (mousePos.X, mousePos.Y);
+				e.Handled = true;
+			}
+		};
 
-			void ApplicationMouseEvent (object sender, MouseEventEventArgs a)
-			{
-				mousePos = new Point (a.MouseEvent.X, a.MouseEvent.Y);
+		Win.MouseClick += (s, e) => {
+			if (e.MouseEvent.Flags == _contextMenu.MouseFlags) {
+				ShowContextMenu (e.MouseEvent.X, e.MouseEvent.Y);
+				e.Handled = true;
 			}
+		};
 
-			Win.WantMousePositionReports = true;
+		Application.MouseEvent += ApplicationMouseEvent;
 
-			Application.Top.Closed += (s,e) => {
-				Thread.CurrentThread.CurrentUICulture = new CultureInfo ("en-US");
-				Application.MouseEvent -= ApplicationMouseEvent;
-			};
+		void ApplicationMouseEvent (object sender, MouseEventEventArgs a)
+		{
+			mousePos = new Point (a.MouseEvent.X, a.MouseEvent.Y);
 		}
 
-		private void ShowContextMenu (int x, int y)
-		{
-			_contextMenu = new ContextMenu (x, y,
-				new MenuBarItem (new MenuItem [] {
+		Win.WantMousePositionReports = true;
+
+		Application.Top.Closed += (s, e) => {
+			Thread.CurrentThread.CurrentUICulture = new CultureInfo ("en-US");
+			Application.MouseEvent -= ApplicationMouseEvent;
+		};
+	}
+
+	private void ShowContextMenu (int x, int y)
+	{
+		_contextMenu = new ContextMenu (x, y,
+			new MenuBarItem (new MenuItem [] {
 					new MenuItem ("_Configuration", "Show configuration", () => MessageBox.Query (50, 5, "Info", "This would open settings dialog", "Ok")),
 					new MenuBarItem ("More options", new MenuItem [] {
-						new MenuItem ("_Setup", "Change settings", () => MessageBox.Query (50, 5, "Info", "This would open setup dialog", "Ok")),
+						new MenuItem ("_Setup", "Change settings", () => MessageBox.Query (50, 5, "Info", "This would open setup dialog", "Ok"), shortcut: KeyCode.T | KeyCode.CtrlMask),
 						new MenuItem ("_Maintenance", "Maintenance mode", () => MessageBox.Query (50, 5, "Info", "This would open maintenance dialog", "Ok")),
 					}),
 					new MenuBarItem ("_Languages", GetSupportedCultures ()),
@@ -111,56 +118,55 @@ namespace UICatalog.Scenarios {
 						},
 					null,
 					new MenuItem ("_Quit", "", () => Application.RequestStop ())
-				})
-			) { ForceMinimumPosToZero = _forceMinimumPosToZero, UseSubMenusSingleFrame = _useSubMenusSingleFrame };
+			})
+		) { ForceMinimumPosToZero = _forceMinimumPosToZero, UseSubMenusSingleFrame = _useSubMenusSingleFrame };
 
-			_tfTopLeft.ContextMenu.ForceMinimumPosToZero = _forceMinimumPosToZero;
-			_tfTopRight.ContextMenu.ForceMinimumPosToZero = _forceMinimumPosToZero;
-			_tfMiddle.ContextMenu.ForceMinimumPosToZero = _forceMinimumPosToZero;
-			_tfBottomLeft.ContextMenu.ForceMinimumPosToZero = _forceMinimumPosToZero;
-			_tfBottomRight.ContextMenu.ForceMinimumPosToZero = _forceMinimumPosToZero;
+		_tfTopLeft.ContextMenu.ForceMinimumPosToZero = _forceMinimumPosToZero;
+		_tfTopRight.ContextMenu.ForceMinimumPosToZero = _forceMinimumPosToZero;
+		_tfMiddle.ContextMenu.ForceMinimumPosToZero = _forceMinimumPosToZero;
+		_tfBottomLeft.ContextMenu.ForceMinimumPosToZero = _forceMinimumPosToZero;
+		_tfBottomRight.ContextMenu.ForceMinimumPosToZero = _forceMinimumPosToZero;
 
-			_contextMenu.Show ();
-		}
+		_contextMenu.Show ();
+	}
 
-		private MenuItem [] GetSupportedCultures ()
-		{
-			List<MenuItem> supportedCultures = new List<MenuItem> ();
-			var index = -1;
+	private MenuItem [] GetSupportedCultures ()
+	{
+		List<MenuItem> supportedCultures = new List<MenuItem> ();
+		var index = -1;
 
-			foreach (var c in _cultureInfos) {
-				var culture = new MenuItem {
-					CheckType = MenuItemCheckStyle.Checked
-				};
-				if (index == -1) {
-					culture.Title = "_English";
-					culture.Help = "en-US";
-					culture.Checked = Thread.CurrentThread.CurrentUICulture.Name == "en-US";
-					CreateAction (supportedCultures, culture);
-					supportedCultures.Add (culture);
-					index++;
-					culture = new MenuItem {
-						CheckType = MenuItemCheckStyle.Checked
-					};
-				}
-				culture.Title = $"_{c.Parent.EnglishName}";
-				culture.Help = c.Name;
-				culture.Checked = Thread.CurrentThread.CurrentUICulture.Name == c.Name;
+		foreach (var c in _cultureInfos) {
+			var culture = new MenuItem {
+				CheckType = MenuItemCheckStyle.Checked
+			};
+			if (index == -1) {
+				culture.Title = "_English";
+				culture.Help = "en-US";
+				culture.Checked = Thread.CurrentThread.CurrentUICulture.Name == "en-US";
 				CreateAction (supportedCultures, culture);
 				supportedCultures.Add (culture);
-			}
-			return supportedCultures.ToArray ();
-
-			void CreateAction (List<MenuItem> supportedCultures, MenuItem culture)
-			{
-				culture.Action += () => {
-					Thread.CurrentThread.CurrentUICulture = new CultureInfo (culture.Help);
-					culture.Checked = true;
-					foreach (var item in supportedCultures) {
-						item.Checked = item.Help == Thread.CurrentThread.CurrentUICulture.Name;
-					}
+				index++;
+				culture = new MenuItem {
+					CheckType = MenuItemCheckStyle.Checked
 				};
 			}
+			culture.Title = $"_{c.Parent.EnglishName}";
+			culture.Help = c.Name;
+			culture.Checked = Thread.CurrentThread.CurrentUICulture.Name == c.Name;
+			CreateAction (supportedCultures, culture);
+			supportedCultures.Add (culture);
+		}
+		return supportedCultures.ToArray ();
+
+		void CreateAction (List<MenuItem> supportedCultures, MenuItem culture)
+		{
+			culture.Action += () => {
+				Thread.CurrentThread.CurrentUICulture = new CultureInfo (culture.Help);
+				culture.Checked = true;
+				foreach (var item in supportedCultures) {
+					item.Checked = item.Help == Thread.CurrentThread.CurrentUICulture.Name;
+				}
+			};
 		}
 	}
-}
+}

+ 5 - 5
UICatalog/Scenarios/CsvEditor.cs

@@ -67,8 +67,8 @@ namespace UICatalog.Scenarios {
 			Application.Top.Add (menu);
 
 			var statusBar = new StatusBar (new StatusItem [] {
-				new StatusItem(Key.CtrlMask | Key.O, "~^O~ Open", () => Open()),
-				new StatusItem(Key.CtrlMask | Key.S, "~^S~ Save", () => Save()),
+				new StatusItem(KeyCode.CtrlMask | KeyCode.O, "~^O~ Open", () => Open()),
+				new StatusItem(KeyCode.CtrlMask | KeyCode.S, "~^S~ Save", () => Save()),
 				new StatusItem(Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit()),
 			});
 			Application.Top.Add (statusBar);
@@ -88,7 +88,7 @@ namespace UICatalog.Scenarios {
 
 			tableView.SelectedCellChanged += OnSelectedCellChanged;
 			tableView.CellActivated += EditCurrentCell;
-			tableView.KeyPressed += TableViewKeyPress;
+			tableView.KeyDown += TableViewKeyPress;
 
 			SetupScrollBar ();
 		}
@@ -465,9 +465,9 @@ namespace UICatalog.Scenarios {
 
 		}
 
-		private void TableViewKeyPress (object sender, KeyEventEventArgs e)
+		private void TableViewKeyPress (object sender, Key e)
 		{
-			if (e.KeyEvent.Key == Key.DeleteChar) {
+			if (e.KeyCode == KeyCode.DeleteChar) {
 
 				if (tableView.FullRowSelect) {
 					// Delete button deletes all rows when in full row mode

+ 13 - 16
UICatalog/Scenarios/DynamicMenuBar.cs

@@ -84,11 +84,11 @@ namespace UICatalog.Scenarios {
 					Height = 4
 				};
 
-				var _txtDelimiter = new TextField (MenuBar.ShortcutDelimiter) {
+				var _txtDelimiter = new TextField (MenuBar.ShortcutDelimiter.ToString()) {
 					X = Pos.Center (),
 					Width = 2,
 				};
-				_txtDelimiter.TextChanged += (s, _) => MenuBar.ShortcutDelimiter = _txtDelimiter.Text;
+				_txtDelimiter.TextChanged += (s, _) => MenuBar.ShortcutDelimiter = _txtDelimiter.Text.ToRunes()[0];
 				_frmDelimiter.Add (_txtDelimiter);
 
 				Add (_frmDelimiter);
@@ -723,30 +723,28 @@ namespace UICatalog.Scenarios {
 					ReadOnly = true
 				};
 				_txtShortcut.KeyDown += (s, e) => {
-					if (!ProcessKey (e.KeyEvent)) {
+					if (!ProcessKey (e)) {
 						return;
 					}
-
-					var k = ShortcutHelper.GetModifiersKey (e.KeyEvent);
-					if (CheckShortcut (k, true)) {
+					if (CheckShortcut (e.KeyCode, true)) {
 						e.Handled = true;
 					}
 				};
 
-				bool ProcessKey (KeyEvent ev)
+				bool ProcessKey (Key ev)
 				{
-					switch (ev.Key) {
-					case Key.CursorUp:
-					case Key.CursorDown:
-					case Key.Tab:
-					case Key.BackTab:
+					switch (ev.KeyCode) {
+					case KeyCode.CursorUp:
+					case KeyCode.CursorDown:
+					case KeyCode.Tab:
+					case KeyCode.Tab | KeyCode.ShiftMask:
 						return false;
 					}
 
 					return true;
 				}
 
-				bool CheckShortcut (Key k, bool pre)
+				bool CheckShortcut (KeyCode k, bool pre)
 				{
 					var m = _menuItem != null ? _menuItem : new MenuItem ();
 					if (pre && !ShortcutHelper.PreShortcutValidation (k)) {
@@ -760,14 +758,13 @@ namespace UICatalog.Scenarios {
 						}
 						return true;
 					}
-					_txtShortcut.Text = ShortcutHelper.GetShortcutTag (k);
+					_txtShortcut.Text = Key.ToString (k, MenuBar.ShortcutDelimiter);// ShortcutHelper.GetShortcutTag (k);
 
 					return true;
 				}
 
 				_txtShortcut.KeyUp += (s, e) => {
-					var k = ShortcutHelper.GetModifiersKey (e.KeyEvent);
-					if (CheckShortcut (k, false)) {
+					if (CheckShortcut (e.KeyCode, false)) {
 						e.Handled = true;
 					}
 				};

+ 540 - 543
UICatalog/Scenarios/DynamicStatusBar.cs

@@ -7,652 +7,649 @@ using System.Reflection;
 using System.Runtime.CompilerServices;
 using Terminal.Gui;
 
-namespace UICatalog.Scenarios {
-	[ScenarioMetadata (Name: "Dynamic StatusBar", Description: "Demonstrates how to add and remove a StatusBar and change items dynamically.")]
-	[ScenarioCategory ("Top Level Windows")]
-	public class DynamicStatusBar : Scenario {
-		public override void Init ()
-		{
-			Application.Init ();
-			Application.Top.Add (new DynamicStatusBarSample () { Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}" });
-		}
+namespace UICatalog.Scenarios;
+[ScenarioMetadata (Name: "Dynamic StatusBar", Description: "Demonstrates how to add and remove a StatusBar and change items dynamically.")]
+[ScenarioCategory ("Top Level Windows")]
+public class DynamicStatusBar : Scenario {
+	public override void Init ()
+	{
+		Application.Init ();
+		Application.Top.Add (new DynamicStatusBarSample () { Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}" });
+	}
 
-		public class DynamicStatusItemList {
-			public string Title { get; set; }
-			public StatusItem StatusItem { get; set; }
+	public class DynamicStatusItemList {
+		public string Title { get; set; }
+		public StatusItem StatusItem { get; set; }
 
-			public DynamicStatusItemList () { }
+		public DynamicStatusItemList () { }
 
-			public DynamicStatusItemList (string title, StatusItem statusItem)
-			{
-				Title = title;
-				StatusItem = statusItem;
-			}
-
-			public override string ToString () => $"{Title}, {StatusItem}";
+		public DynamicStatusItemList (string title, StatusItem statusItem)
+		{
+			Title = title;
+			StatusItem = statusItem;
 		}
 
-		public class DynamicStatusItem {
-			public string title = "New";
-			public string action = "";
-			public string shortcut;
+		public override string ToString () => $"{Title}, {StatusItem}";
+	}
 
-			public DynamicStatusItem () { }
+	public class DynamicStatusItem {
+		public string title = "New";
+		public string action = "";
+		public string shortcut;
 
-			public DynamicStatusItem (string title)
-			{
-				this.title = title;
-			}
+		public DynamicStatusItem () { }
 
-			public DynamicStatusItem (string title, string action, string shortcut = null)
-			{
-				this.title = title;
-				this.action = action;
-				this.shortcut = shortcut;
-			}
+		public DynamicStatusItem (string title)
+		{
+			this.title = title;
 		}
 
-		public class DynamicStatusBarSample : Window {
-			StatusBar _statusBar;
-			StatusItem _currentStatusItem;
-			int _currentSelectedStatusBar = -1;
-			StatusItem _currentEditStatusItem;
-			ListView _lstItems;
-
-			public DynamicStatusItemModel DataContext { get; set; }
-
-			public DynamicStatusBarSample () : base ()
-			{
-				DataContext = new DynamicStatusItemModel ();
-
-				var _frmDelimiter = new FrameView ("Shortcut Delimiter:") {
-					X = Pos.Center (),
-					Y = 0,
-					Width = 25,
-					Height = 4
-				};
-
-				var _txtDelimiter = new TextField (StatusBar.ShortcutDelimiter) {
-					X = Pos.Center (),
-					Width = 2,
-				};
-				_txtDelimiter.TextChanged += (s, _) => StatusBar.ShortcutDelimiter = _txtDelimiter.Text;
-				_frmDelimiter.Add (_txtDelimiter);
-
-				Add (_frmDelimiter);
-
-				var _frmStatusBar = new FrameView ("Items:") {
-					Y = 5,
-					Width = Dim.Percent (50),
-					Height = Dim.Fill (2)
-				};
-
-				var _btnAddStatusBar = new Button ("Add a StatusBar") {
-					Y = 1,
-				};
-				_frmStatusBar.Add (_btnAddStatusBar);
-
-				var _btnRemoveStatusBar = new Button ("Remove a StatusBar") {
-					Y = 1
-				};
-				_btnRemoveStatusBar.X = Pos.AnchorEnd () - (Pos.Right (_btnRemoveStatusBar) - Pos.Left (_btnRemoveStatusBar));
-				_frmStatusBar.Add (_btnRemoveStatusBar);
-
-				var _btnAdd = new Button (" Add  ") {
-					Y = Pos.Top (_btnRemoveStatusBar) + 2,
-				};
-				_btnAdd.X = Pos.AnchorEnd () - (Pos.Right (_btnAdd) - Pos.Left (_btnAdd));
-				_frmStatusBar.Add (_btnAdd);
-
-				_lstItems = new ListView (new List<DynamicStatusItemList> ()) {
-					ColorScheme = Colors.Dialog,
-					Y = Pos.Top (_btnAddStatusBar) + 2,
-					Width = Dim.Fill () - Dim.Width (_btnAdd) - 1,
-					Height = Dim.Fill (),
-				};
-				_frmStatusBar.Add (_lstItems);
-
-				var _btnRemove = new Button ("Remove") {
-					X = Pos.Left (_btnAdd),
-					Y = Pos.Top (_btnAdd) + 1
-				};
-				_frmStatusBar.Add (_btnRemove);
-
-				var _btnUp = new Button ("^") {
-					X = Pos.Right (_lstItems) + 2,
-					Y = Pos.Top (_btnRemove) + 2
-				};
-				_frmStatusBar.Add (_btnUp);
+		public DynamicStatusItem (string title, string action, string shortcut = null)
+		{
+			this.title = title;
+			this.action = action;
+			this.shortcut = shortcut;
+		}
+	}
 
-				var _btnDown = new Button ("v") {
-					X = Pos.Right (_lstItems) + 2,
-					Y = Pos.Top (_btnUp) + 1
-				};
-				_frmStatusBar.Add (_btnDown);
+	public class DynamicStatusBarSample : Window {
+		StatusBar _statusBar;
+		StatusItem _currentStatusItem;
+		int _currentSelectedStatusBar = -1;
+		StatusItem _currentEditStatusItem;
+		ListView _lstItems;
 
-				Add (_frmStatusBar);
+		public DynamicStatusItemModel DataContext { get; set; }
 
-				var _frmStatusBarDetails = new DynamicStatusBarDetails ("StatusBar Item Details:") {
-					X = Pos.Right (_frmStatusBar),
-					Y = Pos.Top (_frmStatusBar),
-					Width = Dim.Fill (),
-					Height = Dim.Fill (4)
-				};
-				Add (_frmStatusBarDetails);
-
-				_btnUp.Clicked += (s,e) => {
-					var i = _lstItems.SelectedItem;
-					var statusItem = DataContext.Items.Count > 0 ? DataContext.Items [i].StatusItem : null;
-					if (statusItem != null) {
-						var items = _statusBar.Items;
-						if (i > 0) {
-							items [i] = items [i - 1];
-							items [i - 1] = statusItem;
-							DataContext.Items [i] = DataContext.Items [i - 1];
-							DataContext.Items [i - 1] = new DynamicStatusItemList (statusItem.Title, statusItem);
-							_lstItems.SelectedItem = i - 1;
-							_statusBar.SetNeedsDisplay ();
-						}
+		public DynamicStatusBarSample () : base ()
+		{
+			DataContext = new DynamicStatusItemModel ();
+
+			var _frmDelimiter = new FrameView ("Shortcut Delimiter:") {
+				X = Pos.Center (),
+				Y = 0,
+				Width = 25,
+				Height = 4
+			};
+
+			var _txtDelimiter = new TextField ($"{StatusBar.ShortcutDelimiter}") {
+				X = Pos.Center (),
+				Width = 2,
+			};
+			_txtDelimiter.TextChanged += (s, _) => StatusBar.ShortcutDelimiter = _txtDelimiter.Text.ToRunes () [0];
+			_frmDelimiter.Add (_txtDelimiter);
+
+			Add (_frmDelimiter);
+
+			var _frmStatusBar = new FrameView ("Items:") {
+				Y = 5,
+				Width = Dim.Percent (50),
+				Height = Dim.Fill (2)
+			};
+
+			var _btnAddStatusBar = new Button ("Add a StatusBar") {
+				Y = 1,
+			};
+			_frmStatusBar.Add (_btnAddStatusBar);
+
+			var _btnRemoveStatusBar = new Button ("Remove a StatusBar") {
+				Y = 1
+			};
+			_btnRemoveStatusBar.X = Pos.AnchorEnd () - (Pos.Right (_btnRemoveStatusBar) - Pos.Left (_btnRemoveStatusBar));
+			_frmStatusBar.Add (_btnRemoveStatusBar);
+
+			var _btnAdd = new Button (" Add  ") {
+				Y = Pos.Top (_btnRemoveStatusBar) + 2,
+			};
+			_btnAdd.X = Pos.AnchorEnd () - (Pos.Right (_btnAdd) - Pos.Left (_btnAdd));
+			_frmStatusBar.Add (_btnAdd);
+
+			_lstItems = new ListView (new List<DynamicStatusItemList> ()) {
+				ColorScheme = Colors.Dialog,
+				Y = Pos.Top (_btnAddStatusBar) + 2,
+				Width = Dim.Fill () - Dim.Width (_btnAdd) - 1,
+				Height = Dim.Fill (),
+			};
+			_frmStatusBar.Add (_lstItems);
+
+			var _btnRemove = new Button ("Remove") {
+				X = Pos.Left (_btnAdd),
+				Y = Pos.Top (_btnAdd) + 1
+			};
+			_frmStatusBar.Add (_btnRemove);
+
+			var _btnUp = new Button ("^") {
+				X = Pos.Right (_lstItems) + 2,
+				Y = Pos.Top (_btnRemove) + 2
+			};
+			_frmStatusBar.Add (_btnUp);
+
+			var _btnDown = new Button ("v") {
+				X = Pos.Right (_lstItems) + 2,
+				Y = Pos.Top (_btnUp) + 1
+			};
+			_frmStatusBar.Add (_btnDown);
+
+			Add (_frmStatusBar);
+
+			var _frmStatusBarDetails = new DynamicStatusBarDetails ("StatusBar Item Details:") {
+				X = Pos.Right (_frmStatusBar),
+				Y = Pos.Top (_frmStatusBar),
+				Width = Dim.Fill (),
+				Height = Dim.Fill (4)
+			};
+			Add (_frmStatusBarDetails);
+
+			_btnUp.Clicked += (s, e) => {
+				var i = _lstItems.SelectedItem;
+				var statusItem = DataContext.Items.Count > 0 ? DataContext.Items [i].StatusItem : null;
+				if (statusItem != null) {
+					var items = _statusBar.Items;
+					if (i > 0) {
+						items [i] = items [i - 1];
+						items [i - 1] = statusItem;
+						DataContext.Items [i] = DataContext.Items [i - 1];
+						DataContext.Items [i - 1] = new DynamicStatusItemList (statusItem.Title, statusItem);
+						_lstItems.SelectedItem = i - 1;
+						_statusBar.SetNeedsDisplay ();
 					}
-				};
-
-				_btnDown.Clicked += (s,e) => {
-					var i = _lstItems.SelectedItem;
-					var statusItem = DataContext.Items.Count > 0 ? DataContext.Items [i].StatusItem : null;
-					if (statusItem != null) {
-						var items = _statusBar.Items;
-						if (i < items.Length - 1) {
-							items [i] = items [i + 1];
-							items [i + 1] = statusItem;
-							DataContext.Items [i] = DataContext.Items [i + 1];
-							DataContext.Items [i + 1] = new DynamicStatusItemList (statusItem.Title, statusItem);
-							_lstItems.SelectedItem = i + 1;
-							_statusBar.SetNeedsDisplay ();
-						}
+				}
+			};
+
+			_btnDown.Clicked += (s, e) => {
+				var i = _lstItems.SelectedItem;
+				var statusItem = DataContext.Items.Count > 0 ? DataContext.Items [i].StatusItem : null;
+				if (statusItem != null) {
+					var items = _statusBar.Items;
+					if (i < items.Length - 1) {
+						items [i] = items [i + 1];
+						items [i + 1] = statusItem;
+						DataContext.Items [i] = DataContext.Items [i + 1];
+						DataContext.Items [i + 1] = new DynamicStatusItemList (statusItem.Title, statusItem);
+						_lstItems.SelectedItem = i + 1;
+						_statusBar.SetNeedsDisplay ();
 					}
-				};
-
-				var _btnOk = new Button ("Ok") {
-					X = Pos.Right (_frmStatusBar) + 20,
-					Y = Pos.Bottom (_frmStatusBarDetails),
-				};
-				Add (_btnOk);
-
-				var _btnCancel = new Button ("Cancel") {
-					X = Pos.Right (_btnOk) + 3,
-					Y = Pos.Top (_btnOk),
-				};
-				_btnCancel.Clicked += (s,e) => {
-					SetFrameDetails (_currentEditStatusItem);
-				};
-				Add (_btnCancel);
-
-				_lstItems.SelectedItemChanged += (s, e) => {
-					SetFrameDetails ();
-				};
+				}
+			};
+
+			var _btnOk = new Button ("Ok") {
+				X = Pos.Right (_frmStatusBar) + 20,
+				Y = Pos.Bottom (_frmStatusBarDetails),
+			};
+			Add (_btnOk);
+
+			var _btnCancel = new Button ("Cancel") {
+				X = Pos.Right (_btnOk) + 3,
+				Y = Pos.Top (_btnOk),
+			};
+			_btnCancel.Clicked += (s, e) => {
+				SetFrameDetails (_currentEditStatusItem);
+			};
+			Add (_btnCancel);
+
+			_lstItems.SelectedItemChanged += (s, e) => {
+				SetFrameDetails ();
+			};
+
+			_btnOk.Clicked += (s, e) => {
+				if (string.IsNullOrEmpty (_frmStatusBarDetails._txtTitle.Text) && _currentEditStatusItem != null) {
+					MessageBox.ErrorQuery ("Invalid title", "Must enter a valid title!.", "Ok");
+				} else if (_currentEditStatusItem != null) {
+					_frmStatusBarDetails._txtTitle.Text = SetTitleText (
+						_frmStatusBarDetails._txtTitle.Text, _frmStatusBarDetails._txtShortcut.Text);
+					var statusItem = new DynamicStatusItem (_frmStatusBarDetails._txtTitle.Text,
+						_frmStatusBarDetails._txtAction.Text,
+						_frmStatusBarDetails._txtShortcut.Text);
+					UpdateStatusItem (_currentEditStatusItem, statusItem, _lstItems.SelectedItem);
+				}
+			};
 
-				_btnOk.Clicked += (s,e) => {
-					if (string.IsNullOrEmpty (_frmStatusBarDetails._txtTitle.Text) && _currentEditStatusItem != null) {
-						MessageBox.ErrorQuery ("Invalid title", "Must enter a valid title!.", "Ok");
-					} else if (_currentEditStatusItem != null) {
-						_frmStatusBarDetails._txtTitle.Text = SetTitleText (
-							_frmStatusBarDetails._txtTitle.Text, _frmStatusBarDetails._txtShortcut.Text);
-						var statusItem = new DynamicStatusItem (_frmStatusBarDetails._txtTitle.Text,
-							_frmStatusBarDetails._txtAction.Text,
-							_frmStatusBarDetails._txtShortcut.Text);
-						UpdateStatusItem (_currentEditStatusItem, statusItem, _lstItems.SelectedItem);
-					}
-				};
+			_btnAdd.Clicked += (s, e) => {
+				if (StatusBar == null) {
+					MessageBox.ErrorQuery ("StatusBar Bar Error", "Must add a StatusBar first!", "Ok");
+					_btnAddStatusBar.SetFocus ();
+					return;
+				}
 
-				_btnAdd.Clicked += (s,e) => {
-					if (StatusBar == null) {
-						MessageBox.ErrorQuery ("StatusBar Bar Error", "Must add a StatusBar first!", "Ok");
-						_btnAddStatusBar.SetFocus ();
-						return;
-					}
+				var frameDetails = new DynamicStatusBarDetails ();
+				var item = frameDetails.EnterStatusItem ();
+				if (item == null) {
+					return;
+				}
 
-					var frameDetails = new DynamicStatusBarDetails ();
-					var item = frameDetails.EnterStatusItem ();
-					if (item == null) {
-						return;
+				StatusItem newStatusItem = CreateNewStatusBar (item);
+				_currentSelectedStatusBar++;
+				_statusBar.AddItemAt (_currentSelectedStatusBar, newStatusItem);
+				DataContext.Items.Add (new DynamicStatusItemList (newStatusItem.Title, newStatusItem));
+				_lstItems.MoveDown ();
+				SetFrameDetails ();
+			};
+
+			_btnRemove.Clicked += (s, e) => {
+				var statusItem = DataContext.Items.Count > 0 ? DataContext.Items [_lstItems.SelectedItem].StatusItem : null;
+				if (statusItem != null) {
+					_statusBar.RemoveItem (_currentSelectedStatusBar);
+					DataContext.Items.RemoveAt (_lstItems.SelectedItem);
+					if (_lstItems.Source.Count > 0 && _lstItems.SelectedItem > _lstItems.Source.Count - 1) {
+						_lstItems.SelectedItem = _lstItems.Source.Count - 1;
 					}
-
-					StatusItem newStatusItem = CreateNewStatusBar (item);
-					_currentSelectedStatusBar++;
-					_statusBar.AddItemAt (_currentSelectedStatusBar, newStatusItem);
-					DataContext.Items.Add (new DynamicStatusItemList (newStatusItem.Title, newStatusItem));
-					_lstItems.MoveDown ();
+					_lstItems.SetNeedsDisplay ();
 					SetFrameDetails ();
-				};
-
-				_btnRemove.Clicked += (s,e) => {
-					var statusItem = DataContext.Items.Count > 0 ? DataContext.Items [_lstItems.SelectedItem].StatusItem : null;
-					if (statusItem != null) {
-						_statusBar.RemoveItem (_currentSelectedStatusBar);
-						DataContext.Items.RemoveAt (_lstItems.SelectedItem);
-						if (_lstItems.Source.Count > 0 && _lstItems.SelectedItem > _lstItems.Source.Count - 1) {
-							_lstItems.SelectedItem = _lstItems.Source.Count - 1;
-						}
-						_lstItems.SetNeedsDisplay ();
-						SetFrameDetails ();
-					}
-				};
-
-				_lstItems.Enter += (s, e) => {
-					var statusItem = DataContext.Items.Count > 0 ? DataContext.Items [_lstItems.SelectedItem].StatusItem : null;
-					SetFrameDetails (statusItem);
-				};
+				}
+			};
 
-				_btnAddStatusBar.Clicked += (s,e) => {
-					if (_statusBar != null) {
-						return;
-					}
+			_lstItems.Enter += (s, e) => {
+				var statusItem = DataContext.Items.Count > 0 ? DataContext.Items [_lstItems.SelectedItem].StatusItem : null;
+				SetFrameDetails (statusItem);
+			};
 
-					_statusBar = new StatusBar ();
-					Add (_statusBar);
-				};
+			_btnAddStatusBar.Clicked += (s, e) => {
+				if (_statusBar != null) {
+					return;
+				}
 
-				_btnRemoveStatusBar.Clicked += (s,e) => {
-					if (_statusBar == null) {
-						return;
-					}
+				_statusBar = new StatusBar ();
+				Add (_statusBar);
+			};
 
-					Remove (_statusBar);
-					_statusBar = null;
-					DataContext.Items = new List<DynamicStatusItemList> ();
-					_currentStatusItem = null;
-					_currentSelectedStatusBar = -1;
-					SetListViewSource (_currentStatusItem, true);
-					SetFrameDetails (null);
-				};
+			_btnRemoveStatusBar.Clicked += (s, e) => {
+				if (_statusBar == null) {
+					return;
+				}
 
-				SetFrameDetails ();
+				Remove (_statusBar);
+				_statusBar = null;
+				DataContext.Items = new List<DynamicStatusItemList> ();
+				_currentStatusItem = null;
+				_currentSelectedStatusBar = -1;
+				SetListViewSource (_currentStatusItem, true);
+				SetFrameDetails (null);
+			};
 
-				var ustringConverter = new UStringValueConverter ();
-				var listWrapperConverter = new ListWrapperConverter ();
+			SetFrameDetails ();
 
-				var lstItems = new Binding (this, "Items", _lstItems, "Source", listWrapperConverter);
+			var ustringConverter = new UStringValueConverter ();
+			var listWrapperConverter = new ListWrapperConverter ();
 
-				void SetFrameDetails (StatusItem statusItem = null)
-				{
-					StatusItem newStatusItem;
+			var lstItems = new Binding (this, "Items", _lstItems, "Source", listWrapperConverter);
 
-					if (statusItem == null) {
-						newStatusItem = DataContext.Items.Count > 0 ? DataContext.Items [_lstItems.SelectedItem].StatusItem : null;
-					} else {
-						newStatusItem = statusItem;
-					}
+			void SetFrameDetails (StatusItem statusItem = null)
+			{
+				StatusItem newStatusItem;
 
-					_currentEditStatusItem = newStatusItem;
-					_frmStatusBarDetails.EditStatusItem (newStatusItem);
-					var f = _btnOk.Enabled == _frmStatusBarDetails.Enabled;
-					if (!f) {
-						_btnOk.Enabled = _frmStatusBarDetails.Enabled;
-						_btnCancel.Enabled = _frmStatusBarDetails.Enabled;
-					}
+				if (statusItem == null) {
+					newStatusItem = DataContext.Items.Count > 0 ? DataContext.Items [_lstItems.SelectedItem].StatusItem : null;
+				} else {
+					newStatusItem = statusItem;
 				}
 
-				void SetListViewSource (StatusItem _currentStatusItem, bool fill = false)
-				{
-					DataContext.Items = new List<DynamicStatusItemList> ();
-					var statusItem = _currentStatusItem;
-					if (!fill) {
-						return;
-					}
-					if (statusItem != null) {
-						foreach (var si in _statusBar.Items) {
-							DataContext.Items.Add (new DynamicStatusItemList (si.Title, si));
-						}
-					}
+				_currentEditStatusItem = newStatusItem;
+				_frmStatusBarDetails.EditStatusItem (newStatusItem);
+				var f = _btnOk.Enabled == _frmStatusBarDetails.Enabled;
+				if (!f) {
+					_btnOk.Enabled = _frmStatusBarDetails.Enabled;
+					_btnCancel.Enabled = _frmStatusBarDetails.Enabled;
 				}
+			}
 
-				StatusItem CreateNewStatusBar (DynamicStatusItem item)
-				{
-					var newStatusItem = new StatusItem (ShortcutHelper.GetShortcutFromTag (
-						item.shortcut, StatusBar.ShortcutDelimiter),
-						item.title, _frmStatusBarDetails.CreateAction (item));
-
-					return newStatusItem;
+			void SetListViewSource (StatusItem _currentStatusItem, bool fill = false)
+			{
+				DataContext.Items = new List<DynamicStatusItemList> ();
+				var statusItem = _currentStatusItem;
+				if (!fill) {
+					return;
 				}
-
-				void UpdateStatusItem (StatusItem _currentEditStatusItem, DynamicStatusItem statusItem, int index)
-				{
-					_currentEditStatusItem = CreateNewStatusBar (statusItem);
-					_statusBar.Items [index] = _currentEditStatusItem;
-					if (DataContext.Items.Count == 0) {
-						DataContext.Items.Add (new DynamicStatusItemList (_currentEditStatusItem.Title, _currentEditStatusItem));
+				if (statusItem != null) {
+					foreach (var si in _statusBar.Items) {
+						DataContext.Items.Add (new DynamicStatusItemList (si.Title, si));
 					}
-					DataContext.Items [index] = new DynamicStatusItemList (_currentEditStatusItem.Title, _currentEditStatusItem);
-					SetFrameDetails (_currentEditStatusItem);
 				}
-
-				//_frmStatusBarDetails.Initialized += (s, e) => _frmStatusBarDetails.Enabled = false;
 			}
 
-			public static string SetTitleText (string title, string shortcut)
+			StatusItem CreateNewStatusBar (DynamicStatusItem item)
 			{
-				var txt = title;
-				var split = title.Split ('~');
-				if (split.Length > 1) {
-					txt = split [2].Trim (); ;
-				}
-				if (string.IsNullOrEmpty (shortcut)) {
-					return txt;
-				}
+				var newStatusItem = new StatusItem (ShortcutHelper.GetShortcutFromTag (
+					item.shortcut, StatusBar.ShortcutDelimiter),
+					item.title, _frmStatusBarDetails.CreateAction (item));
 
-				return $"~{shortcut}~ {txt}";
+				return newStatusItem;
 			}
-		}
 
-		public class DynamicStatusBarDetails : FrameView {
-			public StatusItem _statusItem;
-			public TextField _txtTitle;
-			public TextView _txtAction;
-			public TextField _txtShortcut;
-
-			public DynamicStatusBarDetails (StatusItem statusItem = null) : this (statusItem == null ? "Adding New StatusBar Item." : "Editing StatusBar Item.")
+			void UpdateStatusItem (StatusItem _currentEditStatusItem, DynamicStatusItem statusItem, int index)
 			{
-				_statusItem = statusItem;
+				_currentEditStatusItem = CreateNewStatusBar (statusItem);
+				_statusBar.Items [index] = _currentEditStatusItem;
+				if (DataContext.Items.Count == 0) {
+					DataContext.Items.Add (new DynamicStatusItemList (_currentEditStatusItem.Title, _currentEditStatusItem));
+				}
+				DataContext.Items [index] = new DynamicStatusItemList (_currentEditStatusItem.Title, _currentEditStatusItem);
+				SetFrameDetails (_currentEditStatusItem);
 			}
 
-			public DynamicStatusBarDetails (string title) : base (title)
-			{
-				var _lblTitle = new Label ("Title:") {
-					Y = 1
-				};
-				Add (_lblTitle);
-
-				_txtTitle = new TextField () {
-					X = Pos.Right (_lblTitle) + 4,
-					Y = Pos.Top (_lblTitle),
-					Width = Dim.Fill ()
-				};
-				Add (_txtTitle);
+			//_frmStatusBarDetails.Initialized += (s, e) => _frmStatusBarDetails.Enabled = false;
+		}
 
-				var _lblAction = new Label ("Action:") {
-					X = Pos.Left (_lblTitle),
-					Y = Pos.Bottom (_lblTitle) + 1
-				};
-				Add (_lblAction);
+		public static string SetTitleText (string title, string shortcut)
+		{
+			var txt = title;
+			var split = title.Split ('~');
+			if (split.Length > 1) {
+				txt = split [2].Trim (); ;
+			}
+			if (string.IsNullOrEmpty (shortcut)) {
+				return txt;
+			}
 
-				_txtAction = new TextView () {
-					X = Pos.Left (_txtTitle),
-					Y = Pos.Top (_lblAction),
-					Width = Dim.Fill (),
-					Height = 5
-				};
-				Add (_txtAction);
+			return $"~{shortcut}~ {txt}";
+		}
+	}
 
-				var _lblShortcut = new Label ("Shortcut:") {
-					X = Pos.Left (_lblTitle),
-					Y = Pos.Bottom (_txtAction) + 1
-				};
-				Add (_lblShortcut);
+	public class DynamicStatusBarDetails : FrameView {
+		public StatusItem _statusItem;
+		public TextField _txtTitle;
+		public TextView _txtAction;
+		public TextField _txtShortcut;
 
-				_txtShortcut = new TextField () {
-					X = Pos.X (_txtAction),
-					Y = Pos.Y (_lblShortcut),
-					Width = Dim.Fill (),
-					ReadOnly = true
-				};
-				_txtShortcut.KeyDown += (s, e) => {
-					if (!ProcessKey (e.KeyEvent)) {
-						return;
-					}
+		public DynamicStatusBarDetails (StatusItem statusItem = null) : this (statusItem == null ? "Adding New StatusBar Item." : "Editing StatusBar Item.")
+		{
+			_statusItem = statusItem;
+		}
 
-					var k = ShortcutHelper.GetModifiersKey (e.KeyEvent);
-					if (CheckShortcut (k, true)) {
-						e.Handled = true;
-					}
-				};
+		public DynamicStatusBarDetails (string title) : base (title)
+		{
+			var _lblTitle = new Label ("Title:") {
+				Y = 1
+			};
+			Add (_lblTitle);
+
+			_txtTitle = new TextField () {
+				X = Pos.Right (_lblTitle) + 4,
+				Y = Pos.Top (_lblTitle),
+				Width = Dim.Fill ()
+			};
+			Add (_txtTitle);
+
+			var _lblAction = new Label ("Action:") {
+				X = Pos.Left (_lblTitle),
+				Y = Pos.Bottom (_lblTitle) + 1
+			};
+			Add (_lblAction);
+
+			_txtAction = new TextView () {
+				X = Pos.Left (_txtTitle),
+				Y = Pos.Top (_lblAction),
+				Width = Dim.Fill (),
+				Height = 5
+			};
+			Add (_txtAction);
+
+			var _lblShortcut = new Label ("Shortcut:") {
+				X = Pos.Left (_lblTitle),
+				Y = Pos.Bottom (_txtAction) + 1
+			};
+			Add (_lblShortcut);
+
+			_txtShortcut = new TextField () {
+				X = Pos.X (_txtAction),
+				Y = Pos.Y (_lblShortcut),
+				Width = Dim.Fill (),
+				ReadOnly = true
+			};
+			_txtShortcut.KeyDown += (s, e) => {
+				if (!ProcessKey (e)) {
+					return;
+				}
 
-				bool ProcessKey (KeyEvent ev)
-				{
-					switch (ev.Key) {
-					case Key.CursorUp:
-					case Key.CursorDown:
-					case Key.Tab:
-					case Key.BackTab:
-						return false;
-					}
+				if (CheckShortcut (e.KeyCode, true)) {
+					e.Handled = true;
+				}
+			};
 
-					return true;
+			bool ProcessKey (Key ev)
+			{
+				switch (ev.KeyCode) {
+				case KeyCode.CursorUp:
+				case KeyCode.CursorDown:
+				case KeyCode.Tab:
+				case KeyCode.Tab | KeyCode.ShiftMask:
+					return false;
 				}
 
-				bool CheckShortcut (Key k, bool pre)
-				{
-					var m = _statusItem != null ? _statusItem : new StatusItem (k, "", null);
-					if (pre && !ShortcutHelper.PreShortcutValidation (k)) {
+				return true;
+			}
+
+			bool CheckShortcut (KeyCode k, bool pre)
+			{
+				var m = _statusItem != null ? _statusItem : new StatusItem (k, "", null);
+				if (pre && !ShortcutHelper.PreShortcutValidation (k)) {
+					_txtShortcut.Text = "";
+					return false;
+				}
+				if (!pre) {
+					if (!ShortcutHelper.PostShortcutValidation (ShortcutHelper.GetShortcutFromTag (
+						_txtShortcut.Text, StatusBar.ShortcutDelimiter))) {
 						_txtShortcut.Text = "";
 						return false;
 					}
-					if (!pre) {
-						if (!ShortcutHelper.PostShortcutValidation (ShortcutHelper.GetShortcutFromTag (
-							_txtShortcut.Text, StatusBar.ShortcutDelimiter))) {
-							_txtShortcut.Text = "";
-							return false;
-						}
-						return true;
-					}
-					_txtShortcut.Text = ShortcutHelper.GetShortcutTag (k, StatusBar.ShortcutDelimiter);
-
 					return true;
 				}
+				_txtShortcut.Text = Key.ToString (k, StatusBar.ShortcutDelimiter);//ShortcutHelper.GetShortcutTag (k, StatusBar.ShortcutDelimiter);
 
-				_txtShortcut.KeyUp += (s, e) => {
-					var k = ShortcutHelper.GetModifiersKey (e.KeyEvent);
-					if (CheckShortcut (k, false)) {
-						e.Handled = true;
-					}
-				};
-				Add (_txtShortcut);
-
-				var _btnShortcut = new Button ("Clear Shortcut") {
-					X = Pos.X (_lblShortcut),
-					Y = Pos.Bottom (_txtShortcut) + 1
-				};
-				_btnShortcut.Clicked += (s,e) => {
-					_txtShortcut.Text = "";
-				};
-				Add (_btnShortcut);
+				return true;
 			}
 
-			public DynamicStatusItem EnterStatusItem ()
-			{
-				var valid = false;
-
-				if (_statusItem == null) {
-					var m = new DynamicStatusItem ();
-					_txtTitle.Text = m.title;
-					_txtAction.Text = m.action;
-				} else {
-					EditStatusItem (_statusItem);
+			_txtShortcut.KeyUp += (s, e) => {
+				if (CheckShortcut (e.KeyCode, true)) {
+					e.Handled = true;
 				}
+			};
+			Add (_txtShortcut);
+
+			var _btnShortcut = new Button ("Clear Shortcut") {
+				X = Pos.X (_lblShortcut),
+				Y = Pos.Bottom (_txtShortcut) + 1
+			};
+			_btnShortcut.Clicked += (s, e) => {
+				_txtShortcut.Text = "";
+			};
+			Add (_btnShortcut);
+		}
 
-				var _btnOk = new Button ("Ok") {
-					IsDefault = true,
-				};
-				_btnOk.Clicked += (s,e) => {
-					if (string.IsNullOrEmpty (_txtTitle.Text)) {
-						MessageBox.ErrorQuery ("Invalid title", "Must enter a valid title!.", "Ok");
-					} else {
-						if (!string.IsNullOrEmpty (_txtShortcut.Text)) {
-							_txtTitle.Text = DynamicStatusBarSample.SetTitleText (
-								_txtTitle.Text, _txtShortcut.Text);
-						}
-						valid = true;
-						Application.RequestStop ();
-					}
-				};
-				var _btnCancel = new Button ("Cancel");
-				_btnCancel.Clicked += (s,e) => {
-					_txtTitle.Text = string.Empty;
-					Application.RequestStop ();
-				};
-				var _dialog = new Dialog (_btnOk, _btnCancel) { Title = "Enter the menu details." };
-
-				Width = Dim.Fill ();
-				Height = Dim.Fill () - 1;
-				_dialog.Add (this);
-				_txtTitle.SetFocus ();
-				_txtTitle.CursorPosition = _txtTitle.Text.Length;
-				Application.Run (_dialog);
-
-				if (valid) {
-					return new DynamicStatusItem (_txtTitle.Text, _txtAction.Text, _txtShortcut.Text);
-				} else {
-					return null;
-				}
+		public DynamicStatusItem EnterStatusItem ()
+		{
+			var valid = false;
+
+			if (_statusItem == null) {
+				var m = new DynamicStatusItem ();
+				_txtTitle.Text = m.title;
+				_txtAction.Text = m.action;
+			} else {
+				EditStatusItem (_statusItem);
 			}
 
-			public void EditStatusItem (StatusItem statusItem)
-			{
-				if (statusItem == null) {
-					Enabled = false;
-					CleanEditStatusItem ();
-					return;
+			var _btnOk = new Button ("Ok") {
+				IsDefault = true,
+			};
+			_btnOk.Clicked += (s, e) => {
+				if (string.IsNullOrEmpty (_txtTitle.Text)) {
+					MessageBox.ErrorQuery ("Invalid title", "Must enter a valid title!.", "Ok");
 				} else {
-					Enabled = true;
+					if (!string.IsNullOrEmpty (_txtShortcut.Text)) {
+						_txtTitle.Text = DynamicStatusBarSample.SetTitleText (
+							_txtTitle.Text, _txtShortcut.Text);
+					}
+					valid = true;
+					Application.RequestStop ();
 				}
-				_statusItem = statusItem;
-				_txtTitle.Text = statusItem?.Title ?? "";
-				_txtAction.Text = statusItem != null && statusItem.Action != null ? GetTargetAction (statusItem.Action) : string.Empty;
-				_txtShortcut.Text = ShortcutHelper.GetShortcutTag (statusItem.Shortcut, StatusBar.ShortcutDelimiter) ?? "";
+			};
+			var _btnCancel = new Button ("Cancel");
+			_btnCancel.Clicked += (s, e) => {
+				_txtTitle.Text = string.Empty;
+				Application.RequestStop ();
+			};
+			var _dialog = new Dialog (_btnOk, _btnCancel) { Title = "Enter the menu details." };
+
+			Width = Dim.Fill ();
+			Height = Dim.Fill () - 1;
+			_dialog.Add (this);
+			_txtTitle.SetFocus ();
+			_txtTitle.CursorPosition = _txtTitle.Text.Length;
+			Application.Run (_dialog);
+
+			if (valid) {
+				return new DynamicStatusItem (_txtTitle.Text, _txtAction.Text, _txtShortcut.Text);
+			} else {
+				return null;
 			}
+		}
 
-			void CleanEditStatusItem ()
-			{
-				_txtTitle.Text = "";
-				_txtAction.Text = "";
-				_txtShortcut.Text = "";
+		public void EditStatusItem (StatusItem statusItem)
+		{
+			if (statusItem == null) {
+				Enabled = false;
+				CleanEditStatusItem ();
+				return;
+			} else {
+				Enabled = true;
 			}
+			_statusItem = statusItem;
+			_txtTitle.Text = statusItem?.Title ?? "";
+			_txtAction.Text = statusItem != null && statusItem.Action != null ? GetTargetAction (statusItem.Action) : string.Empty;
+			_txtShortcut.Text = Key.ToString ((KeyCode)statusItem.Shortcut, StatusBar.ShortcutDelimiter);//ShortcutHelper.GetShortcutTag (statusItem.Shortcut, StatusBar.ShortcutDelimiter) ?? "";
+		}
 
-			string GetTargetAction (Action action)
-			{
-				var me = action.Target;
+		void CleanEditStatusItem ()
+		{
+			_txtTitle.Text = "";
+			_txtAction.Text = "";
+			_txtShortcut.Text = "";
+		}
 
-				if (me == null) {
-					throw new ArgumentException ();
-				}
-				object v = new object ();
-				foreach (var field in me.GetType ().GetFields ()) {
-					if (field.Name == "item") {
-						v = field.GetValue (me);
-					}
+		string GetTargetAction (Action action)
+		{
+			var me = action.Target;
+
+			if (me == null) {
+				throw new ArgumentException ();
+			}
+			object v = new object ();
+			foreach (var field in me.GetType ().GetFields ()) {
+				if (field.Name == "item") {
+					v = field.GetValue (me);
 				}
-				return v == null || !(v is DynamicStatusItem item) ? string.Empty : item.action;
 			}
+			return v == null || !(v is DynamicStatusItem item) ? string.Empty : item.action;
+		}
 
-			public Action CreateAction (DynamicStatusItem item)
-			{
-				return new Action (() => MessageBox.ErrorQuery (item.title, item.action, "Ok"));
-			}
+		public Action CreateAction (DynamicStatusItem item)
+		{
+			return new Action (() => MessageBox.ErrorQuery (item.title, item.action, "Ok"));
 		}
+	}
 
-		public class DynamicStatusItemModel : INotifyPropertyChanged {
-			public event PropertyChangedEventHandler PropertyChanged;
+	public class DynamicStatusItemModel : INotifyPropertyChanged {
+		public event PropertyChangedEventHandler PropertyChanged;
 
-			private string statusBar;
-			private List<DynamicStatusItemList> items;
+		private string statusBar;
+		private List<DynamicStatusItemList> items;
 
-			public string StatusBar {
-				get => statusBar;
-				set {
-					if (value != statusBar) {
-						statusBar = value;
-						PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (GetPropertyName ()));
-					}
+		public string StatusBar {
+			get => statusBar;
+			set {
+				if (value != statusBar) {
+					statusBar = value;
+					PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (GetPropertyName ()));
 				}
 			}
+		}
 
-			public List<DynamicStatusItemList> Items {
-				get => items;
-				set {
-					if (value != items) {
-						items = value;
-						PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (GetPropertyName ()));
-					}
+		public List<DynamicStatusItemList> Items {
+			get => items;
+			set {
+				if (value != items) {
+					items = value;
+					PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (GetPropertyName ()));
 				}
 			}
+		}
 
-			public DynamicStatusItemModel ()
-			{
-				Items = new List<DynamicStatusItemList> ();
-			}
-
-			public string GetPropertyName ([CallerMemberName] string propertyName = null)
-			{
-				return propertyName;
-			}
+		public DynamicStatusItemModel ()
+		{
+			Items = new List<DynamicStatusItemList> ();
 		}
 
-		public interface IValueConverter {
-			object Convert (object value, object parameter = null);
+		public string GetPropertyName ([CallerMemberName] string propertyName = null)
+		{
+			return propertyName;
 		}
+	}
 
-		public class Binding {
-			public View Target { get; private set; }
-			public View Source { get; private set; }
+	public interface IValueConverter {
+		object Convert (object value, object parameter = null);
+	}
 
-			public string SourcePropertyName { get; private set; }
-			public string TargetPropertyName { get; private set; }
+	public class Binding {
+		public View Target { get; private set; }
+		public View Source { get; private set; }
 
-			private object sourceDataContext;
-			private PropertyInfo sourceBindingProperty;
-			private IValueConverter valueConverter;
+		public string SourcePropertyName { get; private set; }
+		public string TargetPropertyName { get; private set; }
 
-			public Binding (View source, string sourcePropertyName, View target, string targetPropertyName, IValueConverter valueConverter = null)
-			{
-				Target = target;
-				Source = source;
-				SourcePropertyName = sourcePropertyName;
-				TargetPropertyName = targetPropertyName;
-				sourceDataContext = Source.GetType ().GetProperty ("DataContext").GetValue (Source);
-				sourceBindingProperty = sourceDataContext.GetType ().GetProperty (SourcePropertyName);
-				this.valueConverter = valueConverter;
-				UpdateTarget ();
-
-				var notifier = ((INotifyPropertyChanged)sourceDataContext);
-				if (notifier != null) {
-					notifier.PropertyChanged += (s, e) => {
-						if (e.PropertyName == SourcePropertyName) {
-							UpdateTarget ();
-						}
-					};
-				}
-			}
+		private object sourceDataContext;
+		private PropertyInfo sourceBindingProperty;
+		private IValueConverter valueConverter;
 
-			private void UpdateTarget ()
-			{
-				try {
-					var sourceValue = sourceBindingProperty.GetValue (sourceDataContext);
-					if (sourceValue == null) {
-						return;
+		public Binding (View source, string sourcePropertyName, View target, string targetPropertyName, IValueConverter valueConverter = null)
+		{
+			Target = target;
+			Source = source;
+			SourcePropertyName = sourcePropertyName;
+			TargetPropertyName = targetPropertyName;
+			sourceDataContext = Source.GetType ().GetProperty ("DataContext").GetValue (Source);
+			sourceBindingProperty = sourceDataContext.GetType ().GetProperty (SourcePropertyName);
+			this.valueConverter = valueConverter;
+			UpdateTarget ();
+
+			var notifier = ((INotifyPropertyChanged)sourceDataContext);
+			if (notifier != null) {
+				notifier.PropertyChanged += (s, e) => {
+					if (e.PropertyName == SourcePropertyName) {
+						UpdateTarget ();
 					}
+				};
+			}
+		}
 
-					var finalValue = valueConverter?.Convert (sourceValue) ?? sourceValue;
-
-					var targetProperty = Target.GetType ().GetProperty (TargetPropertyName);
-					targetProperty.SetValue (Target, finalValue);
-				} catch (Exception ex) {
-					MessageBox.ErrorQuery ("Binding Error", $"Binding failed: {ex}.", "Ok");
+		private void UpdateTarget ()
+		{
+			try {
+				var sourceValue = sourceBindingProperty.GetValue (sourceDataContext);
+				if (sourceValue == null) {
+					return;
 				}
+
+				var finalValue = valueConverter?.Convert (sourceValue) ?? sourceValue;
+
+				var targetProperty = Target.GetType ().GetProperty (TargetPropertyName);
+				targetProperty.SetValue (Target, finalValue);
+			} catch (Exception ex) {
+				MessageBox.ErrorQuery ("Binding Error", $"Binding failed: {ex}.", "Ok");
 			}
 		}
+	}
 
-		public class ListWrapperConverter : IValueConverter {
-			public object Convert (object value, object parameter = null)
-			{
-				return new ListWrapper ((IList)value);
-			}
+	public class ListWrapperConverter : IValueConverter {
+		public object Convert (object value, object parameter = null)
+		{
+			return new ListWrapper ((IList)value);
 		}
+	}
 
-		public class UStringValueConverter : IValueConverter {
-			public object Convert (object value, object parameter = null)
-			{
-				var data = Encoding.ASCII.GetBytes (value.ToString ());
-				return StringExtensions.ToString (data);
-			}
+	public class UStringValueConverter : IValueConverter {
+		public object Convert (object value, object parameter = null)
+		{
+			var data = Encoding.ASCII.GetBytes (value.ToString ());
+			return StringExtensions.ToString (data);
 		}
 	}
 }

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است