Browse Source

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 year ago
parent
commit
dcb3b359ad
100 changed files with 11943 additions and 10753 deletions
  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_space_between_method_call_parameter_list_parentheses = false
 csharp_preserve_single_line_blocks = true
 csharp_preserve_single_line_blocks = true
 dotnet_style_require_accessibility_modifiers = never
 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_before_open_square_brackets = true
 csharp_space_between_method_call_name_and_opening_parenthesis = true
 csharp_space_between_method_call_name_and_opening_parenthesis = true
 csharp_space_between_method_declaration_name_and_open_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
     runs-on: windows-latest
     steps:
     steps:
     - name: Checkout
     - name: Checkout
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
 
 
     - name: DocFX Build
     - name: DocFX Build
       working-directory: docfx
       working-directory: docfx

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

@@ -9,15 +9,15 @@ on:
 jobs:
 jobs:
   build_and_test:
   build_and_test:
 
 
-    runs-on: ubuntu-latest
+    runs-on: windows-latest
 
 
     steps:
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     
     
     - name: Setup dotnet
     - name: Setup dotnet
       uses: actions/setup-dotnet@v3
       uses: actions/setup-dotnet@v3
       with:
       with:
-        dotnet-version: 7.0
+        dotnet-version: 8.0
         dotnet-quality: 'ga'
         dotnet-quality: 'ga'
 
 
     - name: Install dependencies
     - name: Install dependencies
@@ -30,7 +30,7 @@ jobs:
     - name: Test
     - name: Test
       run: |
       run: |
         sed -i 's/"stopOnFail": false/"stopOnFail": true/g' UnitTests/xunit.runner.json
         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/
         mv -v UnitTests/TestResults/*/*.* UnitTests/TestResults/
 
 
     # Note: this step is currently not writing to the gist for some reason
     # 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
     runs-on: ubuntu-latest
 
 
     steps:
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
       with:
       with:
         fetch-depth: 0 # fetch-depth is needed for GitVersion
         fetch-depth: 0 # fetch-depth is needed for GitVersion
 
 
@@ -34,7 +34,7 @@ jobs:
     - name: Setup dotnet
     - name: Setup dotnet
       uses: actions/setup-dotnet@v3
       uses: actions/setup-dotnet@v3
       with:
       with:
-        dotnet-version: 7.0
+        dotnet-version: 8.0
         dotnet-quality: 'ga'
         dotnet-quality: 'ga'
         
         
     - name: Install dependencies
     - 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.
    - 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.
 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
 ### Defining new `View` classes
 
 

+ 1 - 1
Example/Example.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
   <PropertyGroup>
     <OutputType>Exe</OutputType>
     <OutputType>Exe</OutputType>
-    <TargetFramework>net7.0</TargetFramework>
+    <TargetFramework>net8.0</TargetFramework>
     <!-- Version numbers are automatically updated by gitversion when a release is released -->
     <!-- 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. -->
     <!-- In the source tree the version will always be 1.0 for all projects. -->
     <!-- Do not modify these. -->
     <!-- 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
 	// 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
 	// 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.
 	// 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]
 	[DataContract]
 	public class LoginViewModel : ReactiveObject {
 	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
 ### 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
 ```cs
 // 'usernameInput' is 'TextField' 
 // 'usernameInput' is 'TextField' 

+ 3 - 3
ReactiveExample/ReactiveExample.csproj

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

+ 172 - 174
Terminal.Gui/Application.cs

@@ -8,6 +8,7 @@ using System.IO;
 using System.Text.Json.Serialization;
 using System.Text.Json.Serialization;
 
 
 namespace Terminal.Gui;
 namespace Terminal.Gui;
+
 /// <summary>
 /// <summary>
 /// A static, singleton class representing the application. This class is the entry point for the application.
 /// A static, singleton class representing the application. This class is the entry point for the application.
 /// </summary>
 /// </summary>
@@ -28,20 +29,9 @@ namespace Terminal.Gui;
 /// </code>
 /// </code>
 /// </example>
 /// </example>
 /// <remarks>
 /// <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>
 /// </remarks>
 public static partial class Application {
 public static partial class Application {
-
 	/// <summary>
 	/// <summary>
 	/// Gets the <see cref="ConsoleDriver"/> that has been selected. See also <see cref="UseSystemConsole"/>.
 	/// Gets the <see cref="ConsoleDriver"/> that has been selected. See also <see cref="UseSystemConsole"/>.
 	/// </summary>
 	/// </summary>
@@ -64,19 +54,19 @@ public static partial class Application {
 	// For Unit testing - ignores UseSystemConsole
 	// For Unit testing - ignores UseSystemConsole
 	internal static bool _forceFakeConsole;
 	internal static bool _forceFakeConsole;
 
 
-	private static List<CultureInfo> _cachedSupportedCultures;
+	static List<CultureInfo> _cachedSupportedCultures;
 
 
 	/// <summary>
 	/// <summary>
 	/// Gets all cultures supported by the application without the invariant language.
 	/// Gets all cultures supported by the application without the invariant language.
 	/// </summary>
 	/// </summary>
 	public static List<CultureInfo> SupportedCultures => _cachedSupportedCultures;
 	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
 		// Get the assembly
-		Assembly assembly = Assembly.GetExecutingAssembly ();
+		var assembly = Assembly.GetExecutingAssembly ();
 
 
 		//Find the location of the assembly
 		//Find the location of the assembly
 		string assemblyLocation = AppDomain.CurrentDomain.BaseDirectory;
 		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 all culture for which satellite folder found with culture code.
 		return culture.Where (cultureInfo =>
 		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 ();
 		).ToList ();
 	}
 	}
 
 
 	#region Initialization (Init/Shutdown)
 	#region Initialization (Init/Shutdown)
-
 	/// <summary>
 	/// <summary>
 	/// Initializes a new instance of <see cref="Terminal.Gui"/> Application. 
 	/// Initializes a new instance of <see cref="Terminal.Gui"/> Application. 
 	/// </summary>
 	/// </summary>
@@ -155,17 +144,19 @@ public static partial class Application {
 		// multiple times. We need to do this because some settings are only
 		// multiple times. We need to do this because some settings are only
 		// valid after a Driver is loaded. In this cases we need just 
 		// valid after a Driver is loaded. In this cases we need just 
 		// `Settings` so we can determine which driver to use.
 		// `Settings` so we can determine which driver to use.
-		ConfigurationManager.Load (true);
-		ConfigurationManager.Apply ();
+		Load (true);
+		Apply ();
 
 
 		Driver ??= Environment.OSVersion.Platform switch {
 		Driver ??= Environment.OSVersion.Platform switch {
 			_ when _forceFakeConsole => new FakeDriver (), // for unit testing only
 			_ when _forceFakeConsole => new FakeDriver (), // for unit testing only
 			_ when UseSystemConsole => new NetDriver (),
 			_ when UseSystemConsole => new NetDriver (),
 			PlatformID.Win32NT or PlatformID.Win32S or PlatformID.Win32Windows => new WindowsDriver (),
 			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 {
 		try {
 			MainLoop = Driver.Init ();
 			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);
 			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 ());
 		SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext ());
 
 
@@ -192,6 +182,14 @@ public static partial class Application {
 		_initialized = true;
 		_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>
 	/// <summary>
 	/// Shutdown an application initialized with <see cref="Init(ConsoleDriver)"/>.
 	/// Shutdown an application initialized with <see cref="Init(ConsoleDriver)"/>.
@@ -203,7 +201,7 @@ public static partial class Application {
 	public static void Shutdown ()
 	public static void Shutdown ()
 	{
 	{
 		ResetState ();
 		ResetState ();
-		ConfigurationManager.PrintJsonErrors ();
+		PrintJsonErrors ();
 	}
 	}
 
 
 	// Encapsulate all setting of initial state for Application; Having
 	// Encapsulate all setting of initial state for Application; Having
@@ -228,13 +226,18 @@ public static partial class Application {
 
 
 		MainLoop?.Dispose ();
 		MainLoop?.Dispose ();
 		MainLoop = null;
 		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;
 		Iteration = null;
 		MouseEvent = null;
 		MouseEvent = null;
 		KeyDown = null;
 		KeyDown = null;
 		KeyUp = null;
 		KeyUp = null;
-		KeyPressed = null;
 		SizeChanging = null;
 		SizeChanging = null;
 		_mainThreadId = -1;
 		_mainThreadId = -1;
 		NotifyNewRunState = null;
 		NotifyNewRunState = null;
@@ -249,11 +252,9 @@ public static partial class Application {
 		// (https://github.com/gui-cs/Terminal.Gui/issues/1084).
 		// (https://github.com/gui-cs/Terminal.Gui/issues/1084).
 		SynchronizationContext.SetSynchronizationContext (syncContext: null);
 		SynchronizationContext.SetSynchronizationContext (syncContext: null);
 	}
 	}
-
 	#endregion Initialization (Init/Shutdown)
 	#endregion Initialization (Init/Shutdown)
 
 
 	#region Run (Begin, Run, End, Stop)
 	#region Run (Begin, Run, End, Stop)
-
 	/// <summary>
 	/// <summary>
 	/// Notify that a new <see cref="RunState"/> was created (<see cref="Begin(Toplevel)"/> was called). The token is created in 
 	/// 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.
 	/// <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);
 				Top.OnLeave (Toplevel);
 			}
 			}
 			if (string.IsNullOrEmpty (Toplevel.Id)) {
 			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) {
 				while (_topLevels.Count > 0 && _topLevels.FirstOrDefault (x => x.Id == id) != null) {
 					count++;
 					count++;
 					id = (_topLevels.Count + count).ToString ();
 					id = (_topLevels.Count + count).ToString ();
@@ -341,9 +342,9 @@ public static partial class Application {
 			Top = Toplevel;
 			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) {
 			if (Toplevel.Visible) {
 				Current = Toplevel;
 				Current = Toplevel;
@@ -351,8 +352,8 @@ public static partial class Application {
 			} else {
 			} else {
 				refreshDriver = false;
 				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;
 			refreshDriver = false;
 			MoveCurrent (Toplevel);
 			MoveCurrent (Toplevel);
 		} else {
 		} else {
@@ -406,7 +407,7 @@ public static partial class Application {
 	/// platform will be used (<see cref="WindowsDriver"/>, <see cref="CursesDriver"/>, or <see cref="NetDriver"/>).
 	/// 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. 
 	/// Must be <see langword="null"/> if <see cref="Init(ConsoleDriver)"/> has already been called. 
 	/// </param>
 	/// </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 (_initialized) {
 			if (Driver != null) {
 			if (Driver != null) {
@@ -426,7 +427,7 @@ public static partial class Application {
 			}
 			}
 		} else {
 		} else {
 			// Init() has NOT been called.
 			// Init() has NOT been called.
-			InternalInit (() => new T (), driver, calledViaRunT: true);
+			InternalInit (() => new T (), driver, true);
 			Run (Top, errorHandler);
 			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>
 	/// <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)
 	public static void Run (Toplevel view, Func<Exception, bool> errorHandler = null)
 	{
 	{
-		var resume = true;
+		bool resume = true;
 		while (resume) {
 		while (resume) {
 #if !DEBUG
 #if !DEBUG
 				try {
 				try {
@@ -520,13 +521,10 @@ public static partial class Application {
 	///   Runs <paramref name="action"/> on the thread that is processing events
 	///   Runs <paramref name="action"/> on the thread that is processing events
 	/// </summary>
 	/// </summary>
 	/// <param name="action">the action to be invoked on the main processing thread.</param>
 	/// <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
 	// 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.
 	// is ProgressBarStyles and it's not clear it needs to.
@@ -556,7 +554,7 @@ public static partial class Application {
 	}
 	}
 
 
 	/// <summary>
 	/// <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>
 	/// </summary>
 	/// <remarks>
 	/// <remarks>
 	///  See also <see cref="Timeout"/>
 	///  See also <see cref="Timeout"/>
@@ -580,26 +578,20 @@ public static partial class Application {
 	// users use async/await on their code
 	// users use async/await on their code
 	//
 	//
 	class MainLoopSyncContext : SynchronizationContext {
 	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)
 		public override void Send (SendOrPostCallback d, object state)
 		{
 		{
 			if (Thread.CurrentThread.ManagedThreadId == _mainThreadId) {
 			if (Thread.CurrentThread.ManagedThreadId == _mainThreadId) {
 				d (state);
 				d (state);
 			} else {
 			} else {
-				var wasExecuted = false;
+				bool wasExecuted = false;
 				Invoke (() => {
 				Invoke (() => {
 					d (state);
 					d (state);
 					wasExecuted = true;
 					wasExecuted = true;
@@ -612,7 +604,7 @@ public static partial class Application {
 	}
 	}
 
 
 	/// <summary>
 	/// <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>
 	/// </summary>
 	/// <param name="state">The state returned by the <see cref="Begin(Toplevel)"/> method.</param>
 	/// <param name="state">The state returned by the <see cref="Begin(Toplevel)"/> method.</param>
 	public static void RunLoop (RunState state)
 	public static void RunLoop (RunState state)
@@ -624,13 +616,18 @@ public static partial class Application {
 			throw new ObjectDisposedException ("state");
 			throw new ObjectDisposedException ("state");
 		}
 		}
 
 
-		var firstIteration = true;
+		bool firstIteration = true;
 		for (state.Toplevel.Running = true; state.Toplevel.Running;) {
 		for (state.Toplevel.Running = true; state.Toplevel.Running;) {
+			MainLoop.Running = true;
 			if (EndAfterFirstIteration && !firstIteration) {
 			if (EndAfterFirstIteration && !firstIteration) {
 				return;
 				return;
 			}
 			}
 			RunIteration (ref state, ref firstIteration);
 			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>
 	/// <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>
 	/// 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)
 	public static void RunIteration (ref RunState state, ref bool firstIteration)
 	{
 	{
-		if (MainLoop.EventsPending ()) {
+		if (MainLoop.EventsPending () && MainLoop.Running) {
 			// Notify Toplevel it's ready
 			// Notify Toplevel it's ready
 			if (firstIteration) {
 			if (firstIteration) {
 				state.Toplevel.OnReady ();
 				state.Toplevel.OnReady ();
@@ -649,7 +646,6 @@ public static partial class Application {
 
 
 			MainLoop.RunIteration ();
 			MainLoop.RunIteration ();
 			Iteration?.Invoke (null, new IterationEventArgs ());
 			Iteration?.Invoke (null, new IterationEventArgs ());
-
 			EnsureModalOrVisibleAlwaysOnTop (state.Toplevel);
 			EnsureModalOrVisibleAlwaysOnTop (state.Toplevel);
 			if (state.Toplevel != Current) {
 			if (state.Toplevel != Current) {
 				OverlappedTop?.OnDeactivate (state.Toplevel);
 				OverlappedTop?.OnDeactivate (state.Toplevel);
@@ -663,7 +659,7 @@ public static partial class Application {
 		firstIteration = false;
 		firstIteration = false;
 
 
 		if (state.Toplevel != Top &&
 		if (state.Toplevel != Top &&
-			(Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded)) {
+		(Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded)) {
 			state.Toplevel.SetNeedsDisplay (state.Toplevel.Frame);
 			state.Toplevel.SetNeedsDisplay (state.Toplevel.Frame);
 			Top.Draw ();
 			Top.Draw ();
 			foreach (var top in _topLevels.Reverse ()) {
 			foreach (var top in _topLevels.Reverse ()) {
@@ -675,16 +671,16 @@ public static partial class Application {
 			}
 			}
 		}
 		}
 		if (_topLevels.Count == 1 && state.Toplevel == Top
 		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)));
 			state.Toplevel.Clear (new Rect (Point.Empty, new Size (Driver.Cols, Driver.Rows)));
 		}
 		}
 
 
 		if (state.Toplevel.NeedsDisplay ||
 		if (state.Toplevel.NeedsDisplay ||
-			state.Toplevel.SubViewNeedsDisplay ||
-			state.Toplevel.LayoutNeeded ||
-			OverlappedChildNeedsDisplay ()) {
+		state.Toplevel.SubViewNeedsDisplay ||
+		state.Toplevel.LayoutNeeded ||
+		OverlappedChildNeedsDisplay ()) {
 			state.Toplevel.Draw ();
 			state.Toplevel.Draw ();
 			state.Toplevel.PositionCursor ();
 			state.Toplevel.PositionCursor ();
 			Driver.Refresh ();
 			Driver.Refresh ();
@@ -692,8 +688,8 @@ public static partial class Application {
 			Driver.UpdateCursor ();
 			Driver.UpdateCursor ();
 		}
 		}
 		if (state.Toplevel != Top &&
 		if (state.Toplevel != Top &&
-			!state.Toplevel.Modal &&
-			(Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded)) {
+		!state.Toplevel.Modal &&
+		(Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded)) {
 			Top.Draw ();
 			Top.Draw ();
 		}
 		}
 	}
 	}
@@ -713,12 +709,12 @@ public static partial class Application {
 	/// </remarks>
 	/// </remarks>
 	public static void RequestStop (Toplevel top = null)
 	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;
 			top = Current;
 		}
 		}
 
 
 		if (OverlappedTop != null && top.IsOverlappedContainer && top?.Running == true
 		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 ();
 			OverlappedTop.RequestStop ();
 		} else if (OverlappedTop != null && top != Current && Current?.Running == true && Current?.Modal == true
 		} else if (OverlappedTop != null && top != Current && Current?.Running == true && Current?.Modal == true
@@ -738,10 +734,10 @@ public static partial class Application {
 			OnNotifyStopRunState (Current);
 			OnNotifyStopRunState (Current);
 			top.Running = false;
 			top.Running = false;
 			OnNotifyStopRunState (top);
 			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);
 			MoveCurrent (top);
 		} else if (OverlappedTop != null && Current != top && Current?.Running == true && !top.Running
 		} else if (OverlappedTop != null && Current != top && Current?.Running == true && !top.Running
@@ -757,7 +753,7 @@ public static partial class Application {
 			OnNotifyStopRunState (Current);
 			OnNotifyStopRunState (Current);
 		} else {
 		} else {
 			Toplevel currentTop;
 			Toplevel currentTop;
-			if (top == Current || (Current?.Modal == true && !top.Modal)) {
+			if (top == Current || Current?.Modal == true && !top.Modal) {
 				currentTop = Current;
 				currentTop = Current;
 			} else {
 			} else {
 				currentTop = top;
 				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 
 		// 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
 		// 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);
 			OverlappedTop.OnChildClosed (runState.Toplevel);
 		}
 		}
 
 
@@ -837,11 +833,10 @@ public static partial class Application {
 		runState.Toplevel = null;
 		runState.Toplevel = null;
 		runState.Dispose ();
 		runState.Dispose ();
 	}
 	}
-
 	#endregion Run (Begin, Run, End)
 	#endregion Run (Begin, Run, End)
 
 
 	#region Toplevel handling
 	#region Toplevel handling
-	static readonly Stack<Toplevel> _topLevels = new Stack<Toplevel> ();
+	static readonly Stack<Toplevel> _topLevels = new ();
 
 
 	/// <summary>
 	/// <summary>
 	/// The <see cref="Toplevel"/> object used for the application on startup (<seealso cref="Application.Top"/>)
 	/// 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)
 	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;
 			return;
 		}
 		}
 
 
@@ -886,8 +881,8 @@ public static partial class Application {
 		if (_topLevels != null) {
 		if (_topLevels != null) {
 			int count = _topLevels.Count;
 			int count = _topLevels.Count;
 			if (count > 0) {
 			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) {
 				foreach (var t in _topLevels) {
 					if (t != Current) {
 					if (t != Current) {
 						if (t != start && t.Visible && t.Frame.Contains (rx, ry)) {
 						if (t != start && t.Visible && t.Frame.Contains (rx, ry)) {
@@ -905,7 +900,7 @@ public static partial class Application {
 
 
 	static View FindTopFromView (View view)
 	static View FindTopFromView (View view)
 	{
 	{
-		View top = view?.SuperView != null && view?.SuperView != Top
+		var top = view?.SuperView != null && view?.SuperView != Top
 			? view.SuperView : view;
 			? view.SuperView : view;
 
 
 		while (top?.SuperView != null && top?.SuperView != Top) {
 		while (top?.SuperView != null && top?.SuperView != Top) {
@@ -923,7 +918,7 @@ public static partial class Application {
 			lock (_topLevels) {
 			lock (_topLevels) {
 				_topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ());
 				_topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ());
 			}
 			}
-			var index = 0;
+			int index = 0;
 			var savedToplevels = _topLevels.ToArray ();
 			var savedToplevels = _topLevels.ToArray ();
 			foreach (var t in savedToplevels) {
 			foreach (var t in savedToplevels) {
 				if (!t.Modal && t != Current && t != top && t != savedToplevels [index]) {
 				if (!t.Modal && t != Current && t != top && t != savedToplevels [index]) {
@@ -941,7 +936,7 @@ public static partial class Application {
 			lock (_topLevels) {
 			lock (_topLevels) {
 				_topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ());
 				_topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ());
 			}
 			}
-			var index = 0;
+			int index = 0;
 			foreach (var t in _topLevels.ToArray ()) {
 			foreach (var t in _topLevels.ToArray ()) {
 				if (!t.Running && t != Current && index > 0) {
 				if (!t.Running && t != Current && index > 0) {
 					lock (_topLevels) {
 					lock (_topLevels) {
@@ -952,10 +947,10 @@ public static partial class Application {
 			}
 			}
 			return false;
 			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) {
 			lock (_topLevels) {
 				_topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ());
 				_topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ());
 				Current = top;
 				Current = top;
@@ -995,7 +990,6 @@ public static partial class Application {
 		Refresh ();
 		Refresh ();
 		return true;
 		return true;
 	}
 	}
-
 	#endregion Toplevel handling
 	#endregion Toplevel handling
 
 
 	#region Mouse handling
 	#region Mouse handling
@@ -1176,9 +1170,9 @@ public static partial class Application {
 		}
 		}
 
 
 		if ((view == null || view == OverlappedTop) &&
 		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 _);
 			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);
 			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
 	#endregion Mouse handling
 
 
 	#region Keyboard handling
 	#region Keyboard handling
-
-	static Key _alternateForwardKey = Key.PageDown | Key.CtrlMask;
+	static Key _alternateForwardKey = new (KeyCode.PageDown | KeyCode.CtrlMask);
 
 
 	/// <summary>
 	/// <summary>
 	/// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.
 	/// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.
 	/// </summary>
 	/// </summary>
-	[SerializableConfigurationProperty (Scope = typeof (SettingsScope)), JsonConverter (typeof (KeyJsonConverter))]
+	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))] [JsonConverter (typeof (KeyJsonConverter))]
 	public static Key AlternateForwardKey {
 	public static Key AlternateForwardKey {
 		get => _alternateForwardKey;
 		get => _alternateForwardKey;
 		set {
 		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>
 	/// <summary>
 	/// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.
 	/// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.
 	/// </summary>
 	/// </summary>
-	[SerializableConfigurationProperty (Scope = typeof (SettingsScope)), JsonConverter (typeof (KeyJsonConverter))]
+	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))] [JsonConverter (typeof (KeyJsonConverter))]
 	public static Key AlternateBackwardKey {
 	public static Key AlternateBackwardKey {
 		get => _alternateBackwardKey;
 		get => _alternateBackwardKey;
 		set {
 		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>
 	/// <summary>
 	/// Gets or sets the key to quit the application.
 	/// Gets or sets the key to quit the application.
 	/// </summary>
 	/// </summary>
-	[SerializableConfigurationProperty (Scope = typeof (SettingsScope)), JsonConverter (typeof (KeyJsonConverter))]
+	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))] [JsonConverter (typeof (KeyJsonConverter))]
 	public static Key QuitKey {
 	public static Key QuitKey {
 		get => _quitKey;
 		get => _quitKey;
 		set {
 		set {
@@ -1359,6 +1352,7 @@ public static partial class Application {
 			}
 			}
 		}
 		}
 	}
 	}
+
 	static void OnQuitKeyChanged (KeyChangedEventArgs e)
 	static void OnQuitKeyChanged (KeyChangedEventArgs e)
 	{
 	{
 		// Duplicate the list so if it changes during enumeration we're safe
 		// Duplicate the list so if it changes during enumeration we're safe
@@ -1368,115 +1362,119 @@ public static partial class Application {
 	}
 	}
 
 
 	/// <summary>
 	/// <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>
 	/// </summary>
 	/// <remarks>
 	/// <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.
 	/// 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>
 	/// </remarks>
-	public static event EventHandler<KeyEventEventArgs> KeyPressed;
+	public static event EventHandler<Key> KeyDown;
 
 
 	/// <summary>
 	/// <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>
 	/// </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>
 	/// <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;
 			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;
 				return true;
 			}
 			}
-			if (topLevel.Modal)
+			if (topLevel.Modal) {
 				break;
 				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;
 		return false;
 	}
 	}
 
 
 	/// <summary>
 	/// <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>
 	/// </summary>
 	/// <remarks>
 	/// <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.
 	/// do not support firing the <see cref="KeyDown"/> and <see cref="KeyUp"/> events.
+	/// <para>
+	/// Fired after <see cref="KeyDown"/>.
+	/// </para>
 	/// </remarks>
 	/// </remarks>
-	public static event EventHandler<KeyEventEventArgs> KeyDown;
+	public static event EventHandler<Key> KeyUp;
 
 
 	/// <summary>
 	/// <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>
 	/// </summary>
 	/// <remarks>
 	/// <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>
 	/// </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>
 	/// <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);
 		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;
 				break;
+			}
 		}
 		}
-
+		return false;
 	}
 	}
-
 	#endregion Keyboard handling
 	#endregion Keyboard handling
 }
 }
 
 
 /// <summary>
 /// <summary>
 /// Event arguments for the <see cref="Application.Iteration"/> event.
 /// Event arguments for the <see cref="Application.Iteration"/> event.
 /// </summary>
 /// </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);
 						Application.Driver.Clipboard.SetClipboardData (value);
 					}
 					}
 					_contents = value;
 					_contents = value;
-				} catch (NotSupportedException e) {
-					throw e;
 				} catch (Exception) {
 				} catch (Exception) {
 					_contents = value;
 					_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;
-using System.Collections.Generic;
 using System.Text.Json;
 using System.Text.Json;
 using System.Text.Json.Serialization;
 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;
 							}
 							}
-							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.Text;
 using System;
 using System;
-using System.Collections.Generic;
 using System.Diagnostics;
 using System.Diagnostics;
-using static Terminal.Gui.ColorScheme;
 using System.Linq;
 using System.Linq;
-using System.Data;
 
 
 namespace Terminal.Gui;
 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"/>
 	/// The contents of the application output. The driver outputs this buffer to the terminal when <see cref="UpdateScreen"/>
 	/// is called.
 	/// is called.
 	/// <remarks>
 	/// <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>
 	/// </remarks>
 	/// </summary>
 	/// </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; }
 	public Cell [,] Contents { get; internal set; }
 
 
 	/// <summary>
 	/// <summary>
@@ -473,39 +461,33 @@ public abstract class ConsoleDriver {
 	#endregion
 	#endregion
 
 
 	#region Mouse and Keyboard
 	#region Mouse and Keyboard
-
 	/// <summary>
 	/// <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>
 	/// </summary>
-	public event EventHandler<KeyEventEventArgs> KeyPressed;
+	public event EventHandler<Key> KeyDown;
 
 
 	/// <summary>
 	/// <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>
 	/// </summary>
 	/// <param name="a"></param>
 	/// <param name="a"></param>
-	public void OnKeyPressed (KeyEventEventArgs a) => KeyPressed?.Invoke (this, a);
+	public void OnKeyDown (Key a) => KeyDown?.Invoke (this, a);
 
 
 	/// <summary>
 	/// <summary>
-	/// Event fired when a key is released.
+	/// Event fired when a key is released. 
 	/// </summary>
 	/// </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>
 	/// <summary>
 	/// Called when a key is released. Fires the <see cref="KeyUp"/> event.
 	/// Called when a key is released. Fires the <see cref="KeyUp"/> event.
 	/// </summary>
 	/// </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>
 	/// <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>
 	/// <summary>
 	/// Event fired when a mouse event occurs.
 	/// 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>
 	/// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Block"/></remarks>
 	BoxFix = 0x02020164,
 	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.Diagnostics;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
 using System.Text;
 using System.Text;
+using Terminal.Gui.ConsoleDrivers;
 using Unix.Terminal;
 using Unix.Terminal;
 
 
 namespace Terminal.Gui;
 namespace Terminal.Gui;
@@ -343,103 +344,82 @@ internal class CursesDriver : ConsoleDriver {
 
 
 	public Curses.Window _window;
 	public Curses.Window _window;
 
 
-	static Key MapCursesKey (int cursesKey)
+	static KeyCode MapCursesKey (int cursesKey)
 	{
 	{
 		switch (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 ()
 	internal void ProcessInput ()
 	{
 	{
 		int wch;
 		int wch;
@@ -448,9 +428,7 @@ internal class CursesDriver : ConsoleDriver {
 		if (code == Curses.ERR) {
 		if (code == Curses.ERR) {
 			return;
 			return;
 		}
 		}
-
-		_keyModifiers = new KeyModifiers ();
-		Key k = Key.Null;
+		KeyCode k = KeyCode.Null;
 
 
 		if (code == Curses.KEY_CODE_YES) {
 		if (code == Curses.KEY_CODE_YES) {
 			while (code == Curses.KEY_CODE_YES && wch == Curses.KeyResize) {
 			while (code == Curses.KEY_CODE_YES && wch == Curses.KeyResize) {
@@ -464,14 +442,14 @@ internal class CursesDriver : ConsoleDriver {
 				int wch2 = wch;
 				int wch2 = wch;
 
 
 				while (wch2 == Curses.KeyMouse) {
 				while (wch2 == Curses.KeyMouse) {
-					KeyEvent key = null;
+					Key kea = null;
 					ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] {
 					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),
 							new ConsoleKeyInfo ('<', 0, false, false, false)
 							new ConsoleKeyInfo ('<', 0, false, false, false)
 						};
 						};
 					code = 0;
 					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;
 				return;
 			}
 			}
@@ -479,27 +457,26 @@ internal class CursesDriver : ConsoleDriver {
 			if (wch >= 277 && wch <= 288) {
 			if (wch >= 277 && wch <= 288) {
 				// Shift+(F1 - F12)
 				// Shift+(F1 - F12)
 				wch -= 12;
 				wch -= 12;
-				k = Key.ShiftMask | MapCursesKey (wch);
+				k = KeyCode.ShiftMask | MapCursesKey (wch);
 			} else if (wch >= 289 && wch <= 300) {
 			} else if (wch >= 289 && wch <= 300) {
 				// Ctrl+(F1 - F12)
 				// Ctrl+(F1 - F12)
 				wch -= 24;
 				wch -= 24;
-				k = Key.CtrlMask | MapCursesKey (wch);
+				k = KeyCode.CtrlMask | MapCursesKey (wch);
 			} else if (wch >= 301 && wch <= 312) {
 			} else if (wch >= 301 && wch <= 312) {
 				// Ctrl+Shift+(F1 - F12)
 				// Ctrl+Shift+(F1 - F12)
 				wch -= 36;
 				wch -= 36;
-				k = Key.CtrlMask | Key.ShiftMask | MapCursesKey (wch);
+				k = KeyCode.CtrlMask | KeyCode.ShiftMask | MapCursesKey (wch);
 			} else if (wch >= 313 && wch <= 324) {
 			} else if (wch >= 313 && wch <= 324) {
 				// Alt+(F1 - F12)
 				// Alt+(F1 - F12)
 				wch -= 48;
 				wch -= 48;
-				k = Key.AltMask | MapCursesKey (wch);
+				k = KeyCode.AltMask | MapCursesKey (wch);
 			} else if (wch >= 325 && wch <= 327) {
 			} else if (wch >= 325 && wch <= 327) {
 				// Shift+Alt+(F1 - F3)
 				// Shift+Alt+(F1 - F3)
 				wch -= 60;
 				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;
 			return;
 		}
 		}
 
 
@@ -510,85 +487,73 @@ internal class CursesDriver : ConsoleDriver {
 			code = Curses.get_wch (out int wch2);
 			code = Curses.get_wch (out int wch2);
 
 
 			if (code == Curses.KEY_CODE_YES) {
 			if (code == Curses.KEY_CODE_YES) {
-				k = Key.AltMask | MapCursesKey (wch);
+				k = KeyCode.AltMask | MapCursesKey (wch);
 			}
 			}
+			Key key = null;
 			if (code == 0) {
 			if (code == 0) {
-				KeyEvent key = null;
 
 
 				// The ESC-number handling, debatable.
 				// The ESC-number handling, debatable.
 				// Simulates the AltMask itself by pressing Alt + Space.
 				// 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) {
 				} else if (wch2 == Curses.KeyCSI) {
 					ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] {
 					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)
 						};
 						};
 					HandleEscSeqResponse (ref code, ref k, ref wch2, ref key, ref cki);
 					HandleEscSeqResponse (ref code, ref k, ref wch2, ref key, ref cki);
 					return;
 					return;
 				} else {
 				} else {
 					// Unfortunately there are no way to differentiate Ctrl+Alt+alfa and Ctrl+Shift+Alt+alfa.
 					// 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) {
 					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) {
 					} else if (wch2 < 256) {
-						k = (Key)wch2;
-						_keyModifiers.Alt = true;
+						k = (KeyCode)wch2 | KeyCode.AltMask;
 					} else {
 					} 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 {
 			} 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) {
 		} else if (wch == Curses.KeyTab) {
 			k = MapCursesKey (wch);
 			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 {
 		} else {
 			// Unfortunately there are no way to differentiate Ctrl+alfa and Ctrl+Shift+alfa.
 			// Unfortunately there are no way to differentiate Ctrl+alfa and Ctrl+Shift+alfa.
-			k = (Key)wch;
+			k = (KeyCode)wch;
 			if (wch == 0) {
 			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;
 		ConsoleKey ck = 0;
 		ConsoleModifiers mod = 0;
 		ConsoleModifiers mod = 0;
@@ -603,15 +568,14 @@ internal class CursesDriver : ConsoleDriver {
 					}
 					}
 					cki = null;
 					cki = null;
 					if (wch2 == 27) {
 					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);
 							false, false, false), cki);
 					}
 					}
 				} else {
 				} else {
 					k = ConsoleKeyMapping.MapConsoleKeyToKey (consoleKeyInfo.Key, out _);
 					k = ConsoleKeyMapping.MapConsoleKeyToKey (consoleKeyInfo.Key, out _);
 					k = ConsoleKeyMapping.MapKeyModifiers (consoleKeyInfo, k);
 					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 {
 			} else {
 				cki = EscSeqUtils.ResizeArray (consoleKeyInfo, cki);
 				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)
 	public override void SendKeys (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control)
 	{
 	{
-		Key key;
+		KeyCode key;
 
 
 		if (consoleKey == ConsoleKey.Packet) {
 		if (consoleKey == ConsoleKey.Packet) {
 			ConsoleModifiers mod = new ConsoleModifiers ();
 			ConsoleModifiers mod = new ConsoleModifiers ();
@@ -760,33 +724,18 @@ internal class CursesDriver : ConsoleDriver {
 			if (control) {
 			if (control) {
 				mod |= ConsoleModifiers.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) {
 			if (mappable) {
-				key = (Key)kchar;
+				key = (KeyCode)cKeyInfo.KeyChar;
 			}
 			}
 		} else {
 		} 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>
 	/// <summary>
 	/// Escape key code (ASCII 27/0x1B).
 	/// Escape key code (ASCII 27/0x1B).
 	/// </summary>
 	/// </summary>
-	public static readonly char KeyEsc = (char)Key.Esc;
+	public static readonly char KeyEsc = (char)KeyCode.Esc;
 
 
 	/// <summary>
 	/// <summary>
 	/// ESC [ - The CSI (Control Sequence Introducer).
 	/// 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.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Text;
 using System.Text;
+using Terminal.Gui.ConsoleDrivers;
 using Rune = System.Text.Rune;
 using Rune = System.Text.Rune;
 
 
 namespace Terminal.Gui;
 namespace Terminal.Gui;
@@ -461,28 +462,6 @@ public static class FakeConsole {
 	public static bool KeyAvailable { get; }
 	public static bool KeyAvailable { get; }
 	//
 	//
 	// Summary:
 	// 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
 	//	Gets a value that indicates whether input has been redirected from the standard
 	//	input stream.
 	//	input stream.
 	//
 	//
@@ -814,17 +793,17 @@ public static class FakeConsole {
 	public static Stack<ConsoleKeyInfo> MockKeyPresses = new Stack<ConsoleKeyInfo> ();
 	public static Stack<ConsoleKeyInfo> MockKeyPresses = new Stack<ConsoleKeyInfo> ();
 
 
 	/// <summary>
 	/// <summary>
-	///  Helper to push a <see cref="Key"/> onto <see cref="MockKeyPresses"/>.
+	///  Helper to push a <see cref="KeyCode"/> onto <see cref="MockKeyPresses"/>.
 	/// </summary>
 	/// </summary>
 	/// <param name="key"></param>
 	/// <param name="key"></param>
-	public static void PushMockKeyPress (Key key)
+	public static void PushMockKeyPress (KeyCode key)
 	{
 	{
 		MockKeyPresses.Push (new ConsoleKeyInfo (
 		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.Diagnostics;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
 using System.Text;
 using System.Text;
+using Terminal.Gui.ConsoleDrivers;
 
 
 // Alias Console to MockConsole so we don't accidentally use Console
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 using Console = Terminal.Gui.FakeConsole;
 
 
 namespace Terminal.Gui;
 namespace Terminal.Gui;
+
 /// <summary>
 /// <summary>
 /// Implements a mock ConsoleDriver for unit testing
 /// Implements a mock ConsoleDriver for unit testing
 /// </summary>
 /// </summary>
@@ -17,9 +19,10 @@ public class FakeDriver : ConsoleDriver {
 #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
 #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
 
 
 	public class Behaviors {
 	public class Behaviors {
-
 		public bool UseFakeClipboard { get; internal set; }
 		public bool UseFakeClipboard { get; internal set; }
+
 		public bool FakeClipboardAlwaysThrowsNotSupportedException { get; internal set; }
 		public bool FakeClipboardAlwaysThrowsNotSupportedException { get; internal set; }
+
 		public bool FakeClipboardIsSupportedAlwaysFalse { get; internal set; }
 		public bool FakeClipboardIsSupportedAlwaysFalse { get; internal set; }
 
 
 		public Behaviors (bool useFakeClipboard = false, bool fakeClipboardAlwaysThrowsNotSupportedException = false, bool fakeClipboardIsSupportedAlwaysTrue = false)
 		public Behaviors (bool useFakeClipboard = false, bool fakeClipboardAlwaysThrowsNotSupportedException = false, bool fakeClipboardIsSupportedAlwaysTrue = false)
@@ -75,9 +78,9 @@ public class FakeDriver : ConsoleDriver {
 		ResizeScreen ();
 		ResizeScreen ();
 		CurrentAttribute = new Attribute (Color.White, Color.Black);
 		CurrentAttribute = new Attribute (Color.White, Color.Black);
 		ClearContents ();
 		ClearContents ();
-		
+
 		_mainLoopDriver = new FakeMainLoop (this);
 		_mainLoopDriver = new FakeMainLoop (this);
-		_mainLoopDriver.KeyPressed = ProcessInput;
+		_mainLoopDriver.MockKeyPressed = MockKeyPressedHandler;
 		return new MainLoop (_mainLoopDriver);
 		return new MainLoop (_mainLoopDriver);
 	}
 	}
 
 
@@ -181,7 +184,6 @@ public class FakeDriver : ConsoleDriver {
 	}
 	}
 
 
 	#region Color Handling
 	#region Color Handling
-
 	///// <remarks>
 	///// <remarks>
 	///// In the FakeDriver, colors are encoded as an int; same as NetDriver
 	///// In the FakeDriver, colors are encoded as an int; same as NetDriver
 	///// However, the foreground color is stored in the most significant 16 bits, 
 	///// However, the foreground color is stored in the most significant 16 bits, 
@@ -196,62 +198,46 @@ public class FakeDriver : ConsoleDriver {
 	//		background: background
 	//		background: background
 	//	);
 	//	);
 	//}
 	//}
-
 	#endregion
 	#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) {
 		switch (keyInfo.Key) {
 		case ConsoleKey.Escape:
 		case ConsoleKey.Escape:
-			return MapKeyModifiers (keyInfo, Key.Esc);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Esc);
 		case ConsoleKey.Tab:
 		case ConsoleKey.Tab:
-			return keyInfo.Modifiers == ConsoleModifiers.Shift ? Key.BackTab : Key.Tab;
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Tab);
 		case ConsoleKey.Clear:
 		case ConsoleKey.Clear:
-			return MapKeyModifiers (keyInfo, Key.Clear);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Clear);
 		case ConsoleKey.Home:
 		case ConsoleKey.Home:
-			return MapKeyModifiers (keyInfo, Key.Home);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Home);
 		case ConsoleKey.End:
 		case ConsoleKey.End:
-			return MapKeyModifiers (keyInfo, Key.End);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.End);
 		case ConsoleKey.LeftArrow:
 		case ConsoleKey.LeftArrow:
-			return MapKeyModifiers (keyInfo, Key.CursorLeft);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorLeft);
 		case ConsoleKey.RightArrow:
 		case ConsoleKey.RightArrow:
-			return MapKeyModifiers (keyInfo, Key.CursorRight);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorRight);
 		case ConsoleKey.UpArrow:
 		case ConsoleKey.UpArrow:
-			return MapKeyModifiers (keyInfo, Key.CursorUp);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorUp);
 		case ConsoleKey.DownArrow:
 		case ConsoleKey.DownArrow:
-			return MapKeyModifiers (keyInfo, Key.CursorDown);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorDown);
 		case ConsoleKey.PageUp:
 		case ConsoleKey.PageUp:
-			return MapKeyModifiers (keyInfo, Key.PageUp);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageUp);
 		case ConsoleKey.PageDown:
 		case ConsoleKey.PageDown:
-			return MapKeyModifiers (keyInfo, Key.PageDown);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageDown);
 		case ConsoleKey.Enter:
 		case ConsoleKey.Enter:
-			return MapKeyModifiers (keyInfo, Key.Enter);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Enter);
 		case ConsoleKey.Spacebar:
 		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:
 		case ConsoleKey.Backspace:
-			return MapKeyModifiers (keyInfo, Key.Backspace);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Backspace);
 		case ConsoleKey.Delete:
 		case ConsoleKey.Delete:
-			return MapKeyModifiers (keyInfo, Key.DeleteChar);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.DeleteChar);
 		case ConsoleKey.Insert:
 		case ConsoleKey.Insert:
-			return MapKeyModifiers (keyInfo, Key.InsertChar);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.InsertChar);
 		case ConsoleKey.PrintScreen:
 		case ConsoleKey.PrintScreen:
-			return MapKeyModifiers (keyInfo, Key.PrintScreen);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PrintScreen);
 
 
 		case ConsoleKey.Oem1:
 		case ConsoleKey.Oem1:
 		case ConsoleKey.Oem2:
 		case ConsoleKey.Oem2:
@@ -267,114 +253,42 @@ public class FakeDriver : ConsoleDriver {
 		case ConsoleKey.OemPlus:
 		case ConsoleKey.OemPlus:
 		case ConsoleKey.OemMinus:
 		case ConsoleKey.OemMinus:
 			if (keyInfo.KeyChar == 0) {
 			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;
 		var key = keyInfo.Key;
 		if (key >= ConsoleKey.A && key <= ConsoleKey.Z) {
 		if (key >= ConsoleKey.A && key <= ConsoleKey.Z) {
 			var delta = key - ConsoleKey.A;
 			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;
 	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/>
 	/// <inheritdoc/>
@@ -410,7 +324,7 @@ public class FakeDriver : ConsoleDriver {
 
 
 	public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)
 	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)
 	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) {
 			if (Col >= 0 && Col < FakeConsole.BufferWidth && Row >= 0 && Row < FakeConsole.BufferHeight) {
 				FakeConsole.SetCursorPosition (Col, Row);
 				FakeConsole.SetCursorPosition (Col, Row);
 			}
 			}
-		} catch (System.IO.IOException) {
-		} catch (ArgumentOutOfRangeException) {
-		}
+		} catch (System.IO.IOException) { } catch (ArgumentOutOfRangeException) { }
 	}
 	}
 
 
 	#region Not Implemented
 	#region Not Implemented
 	public override void Suspend ()
 	public override void Suspend ()
 	{
 	{
-		throw new NotImplementedException ();
+		return;
+		//throw new NotImplementedException ();
 	}
 	}
 	#endregion
 	#endregion
 
 

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

@@ -5,7 +5,7 @@ namespace Terminal.Gui;
 
 
 internal class FakeMainLoop : IMainLoopDriver {
 internal class FakeMainLoop : IMainLoopDriver {
 
 
-	public Action<ConsoleKeyInfo> KeyPressed;
+	public Action<ConsoleKeyInfo> MockKeyPressed;
 
 
 	public FakeMainLoop (ConsoleDriver consoleDriver = null)
 	public FakeMainLoop (ConsoleDriver consoleDriver = null)
 	{
 	{
@@ -31,7 +31,7 @@ internal class FakeMainLoop : IMainLoopDriver {
 	public void Iteration ()
 	public void Iteration ()
 	{
 	{
 		if (FakeConsole.MockKeyPresses.Count > 0) {
 		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;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using System.Text;
 using System.Text;
+using Terminal.Gui.ConsoleDrivers;
 using static Terminal.Gui.NetEvents;
 using static Terminal.Gui.NetEvents;
 
 
 namespace Terminal.Gui;
 namespace Terminal.Gui;
@@ -208,11 +209,11 @@ internal class NetEvents : IDisposable {
 					} catch (OperationCanceledException) {
 					} catch (OperationCanceledException) {
 						return;
 						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);
 							    false, false, false), _cki);
 						}
 						}
 						_isEscSeq = true;
 						_isEscSeq = true;
@@ -223,7 +224,7 @@ internal class NetEvents : IDisposable {
 						_cki = null;
 						_cki = null;
 						_isEscSeq = false;
 						_isEscSeq = false;
 						break;
 						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);
 						ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
 						_cki = null;
 						_cki = null;
 						if (Console.KeyAvailable) {
 						if (Console.KeyAvailable) {
@@ -822,7 +823,7 @@ internal class NetDriver : ConsoleDriver {
 						//	output.Append (combMark);
 						//	output.Append (combMark);
 						//}
 						//}
 						// WriteToConsole (output, ref lastCol, row, ref outputWidth);
 						// 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);
 						WriteToConsole (output, ref lastCol, row, ref outputWidth);
 						SetCursorPosition (col - 1, row);
 						SetCursorPosition (col - 1, row);
 					}
 					}
@@ -997,45 +998,44 @@ internal class NetDriver : ConsoleDriver {
 		var alt = (mod & ConsoleModifiers.Alt) != 0;
 		var alt = (mod & ConsoleModifiers.Alt) != 0;
 		var control = (mod & ConsoleModifiers.Control) != 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) {
 		switch (keyInfo.Key) {
 		case ConsoleKey.Escape:
 		case ConsoleKey.Escape:
-			return MapKeyModifiers (keyInfo, Key.Esc);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Esc);
 		case ConsoleKey.Tab:
 		case ConsoleKey.Tab:
-			return keyInfo.Modifiers == ConsoleModifiers.Shift ? Key.BackTab : Key.Tab;
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Tab);
 		case ConsoleKey.Home:
 		case ConsoleKey.Home:
-			return MapKeyModifiers (keyInfo, Key.Home);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Home);
 		case ConsoleKey.End:
 		case ConsoleKey.End:
-			return MapKeyModifiers (keyInfo, Key.End);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.End);
 		case ConsoleKey.LeftArrow:
 		case ConsoleKey.LeftArrow:
-			return MapKeyModifiers (keyInfo, Key.CursorLeft);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorLeft);
 		case ConsoleKey.RightArrow:
 		case ConsoleKey.RightArrow:
-			return MapKeyModifiers (keyInfo, Key.CursorRight);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorRight);
 		case ConsoleKey.UpArrow:
 		case ConsoleKey.UpArrow:
-			return MapKeyModifiers (keyInfo, Key.CursorUp);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorUp);
 		case ConsoleKey.DownArrow:
 		case ConsoleKey.DownArrow:
-			return MapKeyModifiers (keyInfo, Key.CursorDown);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorDown);
 		case ConsoleKey.PageUp:
 		case ConsoleKey.PageUp:
-			return MapKeyModifiers (keyInfo, Key.PageUp);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageUp);
 		case ConsoleKey.PageDown:
 		case ConsoleKey.PageDown:
-			return MapKeyModifiers (keyInfo, Key.PageDown);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageDown);
 		case ConsoleKey.Enter:
 		case ConsoleKey.Enter:
-			return MapKeyModifiers (keyInfo, Key.Enter);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Enter);
 		case ConsoleKey.Spacebar:
 		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:
 		case ConsoleKey.Backspace:
-			return MapKeyModifiers (keyInfo, Key.Backspace);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Backspace);
 		case ConsoleKey.Delete:
 		case ConsoleKey.Delete:
-			return MapKeyModifiers (keyInfo, Key.DeleteChar);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.DeleteChar);
 		case ConsoleKey.Insert:
 		case ConsoleKey.Insert:
-			return MapKeyModifiers (keyInfo, Key.InsertChar);
+			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.InsertChar);
 
 
 		case ConsoleKey.Oem1:
 		case ConsoleKey.Oem1:
 		case ConsoleKey.Oem2:
 		case ConsoleKey.Oem2:
@@ -1046,79 +1046,85 @@ internal class NetDriver : ConsoleDriver {
 		case ConsoleKey.Oem7:
 		case ConsoleKey.Oem7:
 		case ConsoleKey.Oem8:
 		case ConsoleKey.Oem8:
 		case ConsoleKey.Oem102:
 		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.OemPeriod:
 		case ConsoleKey.OemComma:
 		case ConsoleKey.OemComma:
 		case ConsoleKey.OemPlus:
 		case ConsoleKey.OemPlus:
 		case ConsoleKey.OemMinus:
 		case ConsoleKey.OemMinus:
-			return (Key)((uint)keyInfo.KeyChar);
+			return (KeyCode)((uint)keyInfo.KeyChar);
 		}
 		}
 
 
 		var key = keyInfo.Key;
 		var key = keyInfo.Key;
-		if (key >= ConsoleKey.A && key <= ConsoleKey.Z) {
+		if (key is >= ConsoleKey.A and <= ConsoleKey.Z) {
 			var delta = key - ConsoleKey.A;
 			var delta = key - ConsoleKey.A;
 			if (keyInfo.Modifiers == ConsoleModifiers.Control) {
 			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) {
 			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.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
 				if (keyInfo.KeyChar == 0 || (keyInfo.KeyChar != 0 && keyInfo.KeyChar >= 1 && keyInfo.KeyChar <= 26)) {
 				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;
 			var delta = key - ConsoleKey.D0;
 			if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
 			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) {
 			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.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) {
 		if (key is >= ConsoleKey.F1 and <= ConsoleKey.F12) {
 			var delta = key - ConsoleKey.F1;
 			var delta = key - ConsoleKey.F1;
 			if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
 			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;
 	volatile bool _winSizeChanging;
@@ -1131,19 +1137,10 @@ internal class NetDriver : ConsoleDriver {
 			if (consoleKeyInfo.Key == ConsoleKey.Packet) {
 			if (consoleKeyInfo.Key == ConsoleKey.Packet) {
 				consoleKeyInfo = FromVKPacketToKConsoleKeyInfo (consoleKeyInfo);
 				consoleKeyInfo = FromVKPacketToKConsoleKeyInfo (consoleKeyInfo);
 			}
 			}
-			_keyModifiers = new KeyModifiers ();
 			var map = MapKey (consoleKeyInfo);
 			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;
 			break;
 		case NetEvents.EventType.Mouse:
 		case NetEvents.EventType.Mouse:
 			OnMouseEvent (new MouseEventEventArgs (ToDriverMouse (inputEvent.MouseEvent)));
 			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;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using System.Diagnostics;
 using System.Diagnostics;
+using Terminal.Gui.ConsoleDrivers;
+using static Unix.Terminal.Delegates;
 
 
 namespace Terminal.Gui;
 namespace Terminal.Gui;
 
 
@@ -376,6 +378,8 @@ internal class WindowsConsole {
 		public char UnicodeChar;
 		public char UnicodeChar;
 		[FieldOffset (12), MarshalAs (UnmanagedType.U4)]
 		[FieldOffset (12), MarshalAs (UnmanagedType.U4)]
 		public ControlKeyState dwControlKeyState;
 		public ControlKeyState dwControlKeyState;
+
+		public override readonly string ToString () => $"[KeyEventRecord({(bKeyDown ? "down" : "up")},{wRepeatCount},{wVirtualKeyCode},{wVirtualScanCode},{new Rune (UnicodeChar).MakePrintable ()},{dwControlKeyState})]";
 	}
 	}
 
 
 	[Flags]
 	[Flags]
@@ -595,8 +599,30 @@ internal class WindowsConsole {
 			NumLock = numlock;
 			NumLock = numlock;
 			ScrollLock = scrolllock;
 			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)]
 	[DllImport ("kernel32.dll", SetLastError = true)]
 	static extern IntPtr GetStdHandle (int nStdHandle);
 	static extern IntPtr GetStdHandle (int nStdHandle);
 
 
@@ -875,14 +901,172 @@ internal class WindowsDriver : ConsoleDriver {
 	}
 	}
 #endif
 #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)
 	internal void ProcessInput (WindowsConsole.InputRecord inputEvent)
 	{
 	{
@@ -892,101 +1076,43 @@ internal class WindowsDriver : ConsoleDriver {
 			if (fromPacketKey) {
 			if (fromPacketKey) {
 				inputEvent.KeyEvent = FromVKPacketToKeyEventRecord (inputEvent.KeyEvent);
 				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 {
 			} 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 {
 				} 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;
 			break;
 
 
 		case WindowsConsole.EventType.Mouse:
 		case WindowsConsole.EventType.Mouse:
@@ -1278,8 +1404,6 @@ internal class WindowsDriver : ConsoleDriver {
 		return mouseFlag;
 		return mouseFlag;
 	}
 	}
 
 
-	KeyModifiers _keyModifiers;
-
 	public WindowsConsole.ConsoleKeyInfoEx ToConsoleKeyInfoEx (WindowsConsole.KeyEventRecord keyEvent)
 	public WindowsConsole.ConsoleKeyInfoEx ToConsoleKeyInfoEx (WindowsConsole.KeyEventRecord keyEvent)
 	{
 	{
 		var state = keyEvent.dwControlKeyState;
 		var state = keyEvent.dwControlKeyState;
@@ -1287,33 +1411,12 @@ internal class WindowsDriver : ConsoleDriver {
 		var shift = (state & WindowsConsole.ControlKeyState.ShiftPressed) != 0;
 		var shift = (state & WindowsConsole.ControlKeyState.ShiftPressed) != 0;
 		var alt = (state & (WindowsConsole.ControlKeyState.LeftAltPressed | WindowsConsole.ControlKeyState.RightAltPressed)) != 0;
 		var alt = (state & (WindowsConsole.ControlKeyState.LeftAltPressed | WindowsConsole.ControlKeyState.RightAltPressed)) != 0;
 		var control = (state & (WindowsConsole.ControlKeyState.LeftControlPressed | WindowsConsole.ControlKeyState.RightControlPressed)) != 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)
 	public WindowsConsole.KeyEventRecord FromVKPacketToKeyEventRecord (WindowsConsole.KeyEventRecord keyEvent)
@@ -1334,169 +1437,18 @@ internal class WindowsDriver : ConsoleDriver {
 		    keyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.RightControlPressed)) {
 		    keyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.RightControlPressed)) {
 			mod |= ConsoleModifiers.Control;
 			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 {
 		return new WindowsConsole.KeyEventRecord {
-			UnicodeChar = (char)keyChar,
+			UnicodeChar = cKeyInfo.KeyChar,
 			bKeyDown = keyEvent.bKeyDown,
 			bKeyDown = keyEvent.bKeyDown,
 			dwControlKeyState = keyEvent.dwControlKeyState,
 			dwControlKeyState = keyEvent.dwControlKeyState,
 			wRepeatCount = keyEvent.wRepeatCount,
 			wRepeatCount = keyEvent.wRepeatCount,
-			wVirtualKeyCode = (ushort)virtualKey,
+			wVirtualKeyCode = (ushort)cKeyInfo.Key,
 			wVirtualScanCode = (ushort)scanCode
 			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)
 	public override bool IsRuneSupported (Rune rune)
 	{
 	{
 		return base.IsRuneSupported (rune) && rune.IsBmp;
 		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
 		/// Gets whether the specified coordinates lie within the thickness (inside the bounding rectangle but outside of
 		/// the rectangle described by <see cref="GetInside(Rect)"/>.
 		/// the rectangle described by <see cref="GetInside(Rect)"/>.
 		/// </summary>
 		/// </summary>
+		/// <param name="outside">Describes the location and size of the rectangle that contains the thickness.</param>
 		/// <param name="x"></param>
 		/// <param name="x"></param>
 		/// <param name="y"></param>
 		/// <param name="y"></param>
 		/// <returns><see langword="true"/> if the specified coordinate is within the thickness; <see langword="false"/> otherwise.</returns>
 		/// <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;
 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>
 	/// <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>
 	/// </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.Linq;
 using System.Reflection;
 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
 #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
 #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 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
 #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;
-using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
 using System.Linq;
 using System.Text;
 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>
 	/// <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>
 	/// </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;
 			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 {
 namespace Terminal.Gui {
 	/// <summary>
 	/// <summary>
-	/// Public interface to create a platform specific <see cref="MainLoop"/> driver.
+	/// Interface to create a platform specific <see cref="MainLoop"/> driver.
 	/// </summary>
 	/// </summary>
 	internal interface IMainLoopDriver {
 	internal interface IMainLoopDriver {
 		/// <summary>
 		/// <summary>
@@ -295,7 +295,7 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// <summary>
 		/// Used for unit tests.
 		/// Used for unit tests.
 		/// </summary>
 		/// </summary>
-		internal bool Running { get; private set; }
+		internal bool Running { get; set; }
 
 
 		/// <summary>
 		/// <summary>
 		///   Determines whether there are pending events to be processed.
 		///   Determines whether there are pending events to be processed.

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

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

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

@@ -19,8 +19,8 @@
     <DebugType>portable</DebugType>
     <DebugType>portable</DebugType>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup>
   <PropertyGroup>
-    <TargetFrameworks>net7.0</TargetFrameworks>
-    <LangVersion>11.0</LangVersion>
+    <TargetFrameworks>net8.0</TargetFrameworks>
+    <!--<LangVersion>11.0</LangVersion>-->
     <RootNamespace>Terminal.Gui</RootNamespace>
     <RootNamespace>Terminal.Gui</RootNamespace>
     <AssemblyName>Terminal.Gui</AssemblyName>
     <AssemblyName>Terminal.Gui</AssemblyName>
     <SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage>
     <SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage>
@@ -38,9 +38,8 @@
   <!-- Dependencies -->
   <!-- Dependencies -->
   <!-- =================================================================== -->
   <!-- =================================================================== -->
   <ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
     <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="all" />
     <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.Text.Json" Version="8.0.0" />
     <PackageReference Include="System.Management" Version="8.0.0" />
     <PackageReference Include="System.Management" Version="8.0.0" />
     <PackageReference Include="Wcwidth" Version="2.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)
 		public AppendAutocomplete (TextField textField)
 		{
 		{
 			this.textField = textField;
 			this.textField = textField;
-			SelectionKey = Key.Tab;
+			SelectionKey = KeyCode.Tab;
 
 
 			ColorScheme = new ColorScheme {
 			ColorScheme = new ColorScheme {
 				Normal = new Attribute (Color.DarkGray, Color.Black),
 				Normal = new Attribute (Color.DarkGray, Color.Black),
@@ -54,16 +54,16 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <inheritdoc/>
 		/// <inheritdoc/>
-		public override bool ProcessKey (KeyEvent kb)
+		public override bool ProcessKey (Key a)
 		{
 		{
-			var key = kb.Key;
+			var key = a.KeyCode;
 			if (key == SelectionKey) {
 			if (key == SelectionKey) {
 				return this.AcceptSelectionIfAny ();
 				return this.AcceptSelectionIfAny ();
 			} else
 			} else
-			if (key == Key.CursorUp) {
+			if (key == KeyCode.CursorUp) {
 				return this.CycleSuggestion (1);
 				return this.CycleSuggestion (1);
 			} else
 			} else
-			if (key == Key.CursorDown) {
+			if (key == KeyCode.CursorDown) {
 				return this.CycleSuggestion (-1);
 				return this.CycleSuggestion (-1);
 			} else if (key == CloseKey && Suggestions.Any ()) {
 			} else if (key == CloseKey && Suggestions.Any ()) {
 				ClearSuggestions ();
 				ClearSuggestions ();
@@ -71,7 +71,7 @@ namespace Terminal.Gui {
 				return true;
 				return true;
 			}
 			}
 
 
-			if (char.IsLetterOrDigit ((char)kb.KeyValue)) {
+			if (char.IsLetterOrDigit ((char)a)) {
 				_suspendSuggestions = false;
 				_suspendSuggestions = false;
 			}
 			}
 
 

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

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

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

@@ -53,20 +53,23 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		ColorScheme ColorScheme { get; set; }
 		ColorScheme ColorScheme { get; set; }
 
 
+		// TODO: Update to use Key instead of KeyCode
 		/// <summary>
 		/// <summary>
 		/// The key that the user must press to accept the currently selected autocomplete suggestion
 		/// The key that the user must press to accept the currently selected autocomplete suggestion
 		/// </summary>
 		/// </summary>
-		Key SelectionKey { get; set; }
+		KeyCode SelectionKey { get; set; }
 
 
+		// TODO: Update to use Key instead of KeyCode
 		/// <summary>
 		/// <summary>
 		/// The key that the user can press to close the currently popped autocomplete menu
 		/// The key that the user can press to close the currently popped autocomplete menu
 		/// </summary>
 		/// </summary>
-		Key CloseKey { get; set; }
+		KeyCode CloseKey { get; set; }
 
 
+		// TODO: Update to use Key instead of KeyCode
 		/// <summary>
 		/// <summary>
 		/// The key that the user can press to reopen the currently popped autocomplete menu
 		/// The key that the user can press to reopen the currently popped autocomplete menu
 		/// </summary>
 		/// </summary>
-		Key Reopen { get; set; }
+		KeyCode Reopen { get; set; }
 
 
 		/// <summary>
 		/// <summary>
 		/// The context used by the autocomplete menu.
 		/// 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
 		/// up/down apply to the autocomplete control instead of changing the cursor position in
 		/// the underlying text view.
 		/// the underlying text view.
 		/// </summary>
 		/// </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>
 		/// <returns><c>true</c>if the key can be handled <c>false</c>otherwise.</returns>
-		bool ProcessKey (KeyEvent kb);
+		bool ProcessKey (Key a);
 
 
 		/// <summary>
 		/// <summary>
 		/// Handle mouse events before <see cref="HostControl"/> e.g. to make mouse events like
 		/// 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)
 		public override void RenderOverlay (Point renderAt)
 		{
 		{
 			if (!Context.Canceled && Suggestions.Count > 0 && !Visible && HostControl?.HasFocus == true) {
 			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) {
 			} else if (!Visible || HostControl?.HasFocus == false || Suggestions.Count == 0) {
 				LastPopupPos = null;
 				LastPopupPos = null;
 				Visible = false;
 				Visible = false;
@@ -276,18 +276,18 @@ namespace Terminal.Gui {
 		/// up/down apply to the autocomplete control instead of changing the cursor position in
 		/// up/down apply to the autocomplete control instead of changing the cursor position in
 		/// the underlying text view.
 		/// the underlying text view.
 		/// </summary>
 		/// </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>
 		/// <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;
 				Visible = true;
 				ManipulatePopup ();
 				ManipulatePopup ();
 				closed = false;
 				closed = false;
 				return false;
 				return false;
 			}
 			}
 
 
-			if (kb.Key == Reopen) {
+			if (a.KeyCode == Reopen) {
 				Context.Canceled = false;
 				Context.Canceled = false;
 				return ReopenSuggestions ();
 				return ReopenSuggestions ();
 			}
 			}
@@ -300,19 +300,19 @@ namespace Terminal.Gui {
 				return false;
 				return false;
 			}
 			}
 
 
-			if (kb.Key == Key.CursorDown) {
+			if (a.KeyCode == KeyCode.CursorDown) {
 				MoveDown ();
 				MoveDown ();
 				return true;
 				return true;
 			}
 			}
 
 
-			if (kb.Key == Key.CursorUp) {
+			if (a.KeyCode == KeyCode.CursorUp) {
 				MoveUp ();
 				MoveUp ();
 				return true;
 				return true;
 			}
 			}
 
 
 			// TODO : Revisit this
 			// 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) {
 				if (Suggestions.Count == 0) {
 					Visible = false;
 					Visible = false;
 					if (!closed) {
 					if (!closed) {
@@ -322,11 +322,11 @@ namespace Terminal.Gui {
 				return false;
 				return false;
 			}*/
 			}*/
 
 
-			if (kb.Key == SelectionKey) {
+			if (a.KeyCode == SelectionKey) {
 				return Select ();
 				return Select ();
 			}
 			}
 
 
-			if (kb.Key == CloseKey) {
+			if (a.KeyCode == CloseKey) {
 				Close ();
 				Close ();
 				Context.Canceled = true;
 				Context.Canceled = true;
 				return true;
 				return true;

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

@@ -203,15 +203,15 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <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
 		/// (e.g. letters, numbers, etc) that are valid to pass to this
 		/// class for search filtering.
 		/// class for search filtering.
 		/// </summary>
 		/// </summary>
-		/// <param name="kb"></param>
+		/// <param name="a"></param>
 		/// <returns></returns>
 		/// <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>
 		/// <summary>
-		/// Finds the hotkey and its location in text. 
+		/// Finds the HotKey and its location in text. 
 		/// </summary>
 		/// </summary>
 		/// <param name="text">The text to look in.</param>
 		/// <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>
 		/// 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="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)
 		public static bool FindHotKey (string text, Rune hotKeySpecifier, bool firstUpperCase, out int hotPos, out Key hotKey)
 		{
 		{
 			if (string.IsNullOrEmpty (text) || hotKeySpecifier == (Rune)0xFFFF) {
 			if (string.IsNullOrEmpty (text) || hotKeySpecifier == (Rune)0xFFFF) {
 				hotPos = -1;
 				hotPos = -1;
-				hotKey = Key.Unknown;
+				hotKey = KeyCode.Null;
 				return false;
 				return false;
 			}
 			}
 
 
@@ -983,14 +983,18 @@ namespace Terminal.Gui {
 			if (hot_key != (Rune)0 && hot_pos != -1) {
 			if (hot_key != (Rune)0 && hot_pos != -1) {
 				hotPos = hot_pos;
 				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;
 					return true;
 				}
 				}
 			}
 			}
 
 
 			hotPos = -1;
 			hotPos = -1;
-			hotKey = Key.Unknown;
+			hotKey = KeyCode.Null;
 			return false;
 			return false;
 		}
 		}
 
 
@@ -1047,7 +1051,7 @@ namespace Terminal.Gui {
 		TextAlignment _textAlignment;
 		TextAlignment _textAlignment;
 		VerticalTextAlignment _textVerticalAlignment;
 		VerticalTextAlignment _textVerticalAlignment;
 		TextDirection _textDirection;
 		TextDirection _textDirection;
-		Key _hotKey;
+		Key _hotKey = new Key ();
 		int _hotKeyPos = -1;
 		int _hotKeyPos = -1;
 		Size _size;
 		Size _size;
 		private bool _autoSize;
 		private bool _autoSize;
@@ -1225,17 +1229,17 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		public Rune HotKeySpecifier { get; set; } = (Rune)0xFFFF;
 		public Rune HotKeySpecifier { get; set; } = (Rune)0xFFFF;
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		public int HotKeyPos { get => _hotKeyPos; internal set => _hotKeyPos = value; }
 		public int HotKeyPos { get => _hotKeyPos; internal set => _hotKeyPos = value; }
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		public Key HotKey {
 		public Key HotKey {
 			get => _hotKey;
 			get => _hotKey;
@@ -1287,10 +1291,10 @@ namespace Terminal.Gui {
 					NeedsFormat = false;
 					NeedsFormat = false;
 					return _lines;
 					return _lines;
 				}
 				}
-
+				
 				if (NeedsFormat) {
 				if (NeedsFormat) {
 					var shown_text = _text;
 					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;
 						HotKey = newHotKey;
 						shown_text = RemoveHotKeySpecifier (Text, _hotKeyPos, HotKeySpecifier);
 						shown_text = RemoveHotKeySpecifier (Text, _hotKeyPos, HotKeySpecifier);
 						shown_text = ReplaceHotKeyWithTag (shown_text, _hotKeyPos);
 						shown_text = ReplaceHotKeyWithTag (shown_text, _hotKeyPos);

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

@@ -534,7 +534,7 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// Removes the <see cref="SetNeedsLayout"/> setting on this view.
+		/// Indicates that the view does not need to be laid out.
 		/// </summary>
 		/// </summary>
 		protected void ClearLayoutNeeded ()
 		protected void ClearLayoutNeeded ()
 		{
 		{
@@ -947,7 +947,7 @@ namespace Terminal.Gui {
 		/// response to the container view or terminal resizing.
 		/// response to the container view or terminal resizing.
 		/// </summary>
 		/// </summary>
 		/// <remarks>
 		/// <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>
 		/// </remarks>
 		public virtual void LayoutSubviews ()
 		public virtual void LayoutSubviews ()
 		{
 		{

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

@@ -8,22 +8,24 @@ namespace Terminal.Gui {
 	#region API Docs
 	#region API Docs
 	/// <summary>
 	/// <summary>
 	/// View is the base class for all views on the screen and represents a visible element that can render itself and 
 	/// 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>
 	/// </summary>
 	/// <remarks>
 	/// <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>
 	/// <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.
 	/// <see cref="Enabled"/>, <see cref="Visible"/>, and <see cref="CanFocus"/> will receive focus.
 	/// </para>
 	/// </para>
 	/// <para>
 	/// <para>
@@ -110,7 +112,9 @@ namespace Terminal.Gui {
 	///     to override base class layout code optimally by doing so only on first run,
 	///     to override base class layout code optimally by doing so only on first run,
 	///     instead of on every run.
 	///     instead of on every run.
 	///   </para>
 	///   </para>
-	/// </remarks>
+	/// <para>
+	///	See <see href="../docs/keyboard.md">for an overview of View keyboard handling.</see>
+	/// </para>	/// </remarks>
 	#endregion API Docs
 	#endregion API Docs
 	public partial class View : Responder, ISupportInitializeNotification {
 	public partial class View : Responder, ISupportInitializeNotification {
 
 
@@ -220,7 +224,6 @@ namespace Terminal.Gui {
 			TextFormatter.HotKeyChanged += TextFormatter_HotKeyChanged;
 			TextFormatter.HotKeyChanged += TextFormatter_HotKeyChanged;
 			TextDirection = direction;
 			TextDirection = direction;
 
 
-			_shortcutHelper = new ShortcutHelper ();
 			CanFocus = false;
 			CanFocus = false;
 			TabIndex = -1;
 			TabIndex = -1;
 			TabStop = false;
 			TabStop = false;
@@ -231,6 +234,8 @@ namespace Terminal.Gui {
 			Frame = rect.IsEmpty ? TextFormatter.CalcRect (0, 0, text, direction) : rect;
 			Frame = rect.IsEmpty ? TextFormatter.CalcRect (0, 0, text, direction) : rect;
 			OnResizeNeeded ();
 			OnResizeNeeded ();
 
 
+			AddCommands ();
+
 			CreateFrames ();
 			CreateFrames ();
 
 
 			LayoutFrames ();
 			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>
 		/// <param name="regionScreen">The screen-relative rectangle to clear.</param>
 		public void Clear (Rect regionScreen)
 		public void Clear (Rect regionScreen)
 		{
 		{
+			if (Driver == null) {
+				return;
+			}
 			var prev = Driver.SetAttribute (GetNormalColor ());
 			var prev = Driver.SetAttribute (GetNormalColor ());
 			Driver.FillRect (regionScreen);
 			Driver.FillRect (regionScreen);
 			Driver.SetAttribute (prev);
 			Driver.SetAttribute (prev);

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

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

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

@@ -1,464 +1,685 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.ComponentModel;
 using System.Linq;
 using System.Linq;
 using System.Text;
 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;
 			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 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>
 		/// </summary>
 		public TextFormatter TextFormatter { get; set; }
 		public TextFormatter TextFormatter { get; set; }
 
 
-		void TextFormatter_HotKeyChanged (object sender, KeyChangedEventArgs e)
-		{
-			HotKeyChanged?.Invoke (this, e);
-		}
-
 		/// <summary>
 		/// <summary>
 		/// Can be overridden if the <see cref="Terminal.Gui.TextFormatter.Text"/> has
 		/// Can be overridden if the <see cref="Terminal.Gui.TextFormatter.Text"/> has
 		///  different format than the default.
 		///  different format than the default.

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

@@ -8,304 +8,229 @@
 using System;
 using System;
 using System.Text;
 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>
 	/// <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>
 	/// </summary>
 	/// <remarks>
 	/// <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>
 	/// </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 ();
 			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 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;
 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>
 	/// <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>
 	/// </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>
 		/// </summary>
 		private void AddKeyBindings ()
 		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/>
 		///<inheritdoc/>
@@ -250,16 +250,6 @@ namespace Terminal.Gui {
 			return true;
 			return true;
 		}
 		}
 
 
-		///<inheritdoc/>
-		public override bool ProcessKey (KeyEvent kb)
-		{
-			var result = InvokeKeybindings (kb);
-			if (result != null)
-				return (bool)result;
-
-			return false;
-		}
-
 		///<inheritdoc/>
 		///<inheritdoc/>
 		public override bool MouseEvent (MouseEvent me)
 		public override bool MouseEvent (MouseEvent me)
 		{
 		{

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

@@ -343,16 +343,16 @@ namespace Terminal.Gui {
 			AddCommand (Command.UnixEmulation, () => UnixEmulation ());
 			AddCommand (Command.UnixEmulation, () => UnixEmulation ());
 
 
 			// Default keybindings for this view
 			// 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;
 		private bool isShow = false;
@@ -544,16 +544,6 @@ namespace Terminal.Gui {
 			Driver.AddRune (CM.Glyphs.DownArrow);
 			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 ()
 		bool UnixEmulation ()
 		{
 		{
 			// Unix emulation
 			// 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.Linq;
 using System.Text;
 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>
 	/// <summary>
-	///   Simple Date editing <see cref="View"/>
+	///   DateChanged event, raised when the <see cref="Date"/> property has changed.
 	/// </summary>
 	/// </summary>
 	/// <remarks>
 	/// <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>
 	/// </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;
 				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 {
 				} 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 {
 				} 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;
 			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;
 			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/>
 		///<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);
 				Application.RequestStop (this);
 				return true;
 				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) {
 			this.btnOk = new Button (Style.OkButtonText) {
 				Y = Pos.AnchorEnd (1),
 				Y = Pos.AnchorEnd (1),
-				X = Pos.Function (CalculateOkButtonPosX)
+				X = Pos.Function (CalculateOkButtonPosX),
+				IsDefault = true
 			};
 			};
 			this.btnOk.Clicked += (s, e) => this.Accept (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) {
 			this.btnCancel = new Button (Strings.btnCancel) {
 				Y = Pos.AnchorEnd (1),
 				Y = Pos.AnchorEnd (1),
 				X = Pos.Right (btnOk) + 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) => {
 			this.btnCancel.Clicked += (s, e) => {
 				Application.RequestStop ();
 				Application.RequestStop ();
@@ -179,11 +180,11 @@ namespace Terminal.Gui {
 				Width = Dim.Fill (0),
 				Width = Dim.Fill (0),
 				CaptionColor = new Color (Color.Black)
 				CaptionColor = new Color (Color.Black)
 			};
 			};
-			this.tbPath.KeyPressed += (s, k) => {
+			this.tbPath.KeyDown += (s, k) => {
 
 
 				ClearFeedback ();
 				ClearFeedback ();
 
 
-				this.AcceptIf (k, Key.Enter);
+				this.AcceptIf (k, KeyCode.Enter);
 
 
 				this.SuppressIfBadChar (k);
 				this.SuppressIfBadChar (k);
 			};
 			};
@@ -207,7 +208,7 @@ namespace Terminal.Gui {
 				FullRowSelect = true,
 				FullRowSelect = true,
 				CollectionNavigator = new FileDialogCollectionNavigator (this)
 				CollectionNavigator = new FileDialogCollectionNavigator (this)
 			};
 			};
-			this.tableView.AddKeyBinding (Key.Space, Command.ToggleChecked);
+			this.tableView.KeyBindings.Add (KeyCode.Space, Command.ToggleChecked);
 			this.tableView.MouseClick += OnTableViewMouseClick;
 			this.tableView.MouseClick += OnTableViewMouseClick;
 			tableView.Style.InvertSelectedCellFirstCharacter = true;
 			tableView.Style.InvertSelectedCellFirstCharacter = true;
 			Style.TableStyle = tableView.Style;
 			Style.TableStyle = tableView.Style;
@@ -228,16 +229,16 @@ namespace Terminal.Gui {
 			typeStyle.MinWidth = 6;
 			typeStyle.MinWidth = 6;
 			typeStyle.ColorGetter = this.ColorGetter;
 			typeStyle.ColorGetter = this.ColorGetter;
 
 
-			this.tableView.KeyPressed += (s, k) => {
+			this.tableView.KeyDown += (s, k) => {
 				if (this.tableView.SelectedRow <= 0) {
 				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) {
 				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) {
 				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) {
 				if (k.Handled) {
@@ -277,6 +278,7 @@ namespace Terminal.Gui {
 				CaptionColor = new Color (Color.Black),
 				CaptionColor = new Color (Color.Black),
 				Width = 30,
 				Width = 30,
 				Y = Pos.AnchorEnd (1),
 				Y = Pos.AnchorEnd (1),
+				HotKey = KeyCode.F | KeyCode.AltMask
 			};
 			};
 			spinnerView = new SpinnerView () {
 			spinnerView = new SpinnerView () {
 				X = Pos.Right (tbFind) + 1,
 				X = Pos.Right (tbFind) + 1,
@@ -285,22 +287,22 @@ namespace Terminal.Gui {
 			};
 			};
 
 
 			tbFind.TextChanged += (s, o) => RestartSearch ();
 			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 ();
 					RestartSearch ();
 					o.Handled = true;
 					o.Handled = true;
 				}
 				}
 
 
-				if (o.KeyEvent.Key == Key.Esc) {
+				if (o.KeyCode == KeyCode.Esc) {
 					if (CancelSearch ()) {
 					if (CancelSearch ()) {
 						o.Handled = true;
 						o.Handled = true;
 					}
 					}
 				}
 				}
 				if (tbFind.CursorIsAtEnd ()) {
 				if (tbFind.CursorIsAtEnd ()) {
-					NavigateIf (o, Key.CursorRight, btnCancel);
+					NavigateIf (o, KeyCode.CursorRight, btnCancel);
 				}
 				}
 				if (tbFind.CursorIsAtStart ()) {
 				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.tbPath.TextChanged += (s, e) => this.PathChanged ();
 
 
 			this.tableView.CellActivated += this.CellActivate;
 			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.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) => {
 			this.treeView.KeyDown += (s, k) => {
 
 
 				var selected = treeView.SelectedObject;
 				var selected = treeView.SelectedObject;
 				if (selected != null) {
 				if (selected != null) {
 					if (!treeView.CanExpand (selected) || treeView.IsExpanded (selected)) {
 					if (!treeView.CanExpand (selected) || treeView.IsExpanded (selected)) {
-						this.NavigateIf (k, Key.CursorRight, this.tableView);
+						this.NavigateIf (k, KeyCode.CursorRight, this.tableView);
 					} else
 					} else
 					if (treeView.GetObjectRow (selected) == 0) {
 					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;
 					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 ()
 		private void RestartSearch ()
 		{
 		{
 			if (disposed || State?.Directory == null) {
 			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
 			// don't let user type bad letters
-			var ch = (char)k.KeyEvent.KeyValue;
+			var ch = (char)k;
 
 
 			if (badChars.Contains (ch)) {
 			if (badChars.Contains (ch)) {
 				k.Handled = true;
 				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 ();
 				this.tbPath.FocusFirst ();
 
 
 				// let that keystroke go through on the tbPath instead
 				// let that keystroke go through on the tbPath instead
@@ -794,9 +799,9 @@ namespace Terminal.Gui {
 			return false;
 			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;
 				keyEvent.Handled = true;
 
 
 				// User hit Enter in text box so probably wants the
 				// User hit Enter in text box so probably wants the
@@ -880,19 +885,9 @@ namespace Terminal.Gui {
 			Application.RequestStop ();
 			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 ();
 				to.FocusFirst ();
 				if (to == tbPath) {
 				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 ();
 				return this.history.Back ();
 			}
 			}
-			if (keyEvent.Key == (Key.ShiftMask | Key.Backspace)) {
+			if (keyEvent.KeyCode == (KeyCode.ShiftMask | KeyCode.Backspace)) {
 				return this.history.Forward ();
 				return this.history.Forward ();
 			}
 			}
 
 
-			if (keyEvent.Key == Key.DeleteChar) {
+			if (keyEvent.KeyCode == KeyCode.DeleteChar) {
 
 
 				Delete ();
 				Delete ();
 				return true;
 				return true;
 			}
 			}
 
 
-			if (keyEvent.Key == (Key.CtrlMask | Key.R)) {
+			if (keyEvent.KeyCode == (KeyCode.CtrlMask | KeyCode.R)) {
 
 
 				Rename ();
 				Rename ();
 				return true;
 				return true;
 			}
 			}
 
 
-			if (keyEvent.Key == (Key.CtrlMask | Key.N)) {
+			if (keyEvent.KeyCode == (KeyCode.CtrlMask | KeyCode.N)) {
 				New ();
 				New ();
 				return true;
 				return true;
 			}
 			}

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

@@ -17,7 +17,7 @@ namespace Terminal.Gui {
 	public interface IAnnotation {
 	public interface IAnnotation {
 		/// <summary>
 		/// <summary>
 		/// True if annotation should be drawn before <see cref="ISeries"/>.  This
 		/// 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.
 		/// of this annotation.
 		/// </summary>
 		/// </summary>
 		bool BeforeSeries { get; }
 		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.PageUp, () => { PageUp (); return true; });
 			AddCommand (Command.PageDown, () => { PageDown (); 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)
 			// 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>
 		/// <summary>
@@ -243,18 +243,6 @@ namespace Terminal.Gui {
 			return base.OnEnter (view);
 			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>
 		/// <summary>
 		/// Scrolls the graph up 1 page
 		/// Scrolls the graph up 1 page
 		/// </summary>
 		/// </summary>

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

@@ -10,627 +10,655 @@ using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Text;
 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>
 	/// <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>
 	/// </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 ());
 						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;
 			return true;
 		}
 		}
-
-		bool MoveLeft ()
-		{
+		if (position - 1 < DisplayStart) {
+			SetDisplayStart (displayStart - bytesPerLine);
+			SetNeedsDisplay ();
+		} else {
 			RedisplayLine (position);
 			RedisplayLine (position);
-			if (leftSide) {
-				if (!firstNibble) {
-					firstNibble = true;
-					return true;
-				}
+		}
+		position--;
+
+		return true;
+	}
+
+	bool MoveRight ()
+	{
+		RedisplayLine (position);
+		if (leftSide) {
+			if (firstNibble) {
 				firstNibble = false;
 				firstNibble = false;
-			}
-			if (position == 0)
 				return true;
 				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);
 			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);
 			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;
 			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;
 			Height = 1;
 			AutoSize = autosize;
 			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>
 		/// <summary>
 		///   The event fired when the user clicks the primary mouse button within the Bounds of this <see cref="View"/>
 		///   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)
 		///   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);
 			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>
 		/// <summary>
 		/// Virtual method to invoke the <see cref="Clicked"/> event.
 		/// Virtual method to invoke the <see cref="Clicked"/> event.
 		/// </summary>
 		/// </summary>

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

@@ -166,9 +166,9 @@ namespace Terminal.Gui {
 			set {
 			set {
 				allowsMarking = value;
 				allowsMarking = value;
 				if (allowsMarking) {
 				if (allowsMarking) {
-					AddKeyBinding (Key.Space, Command.ToggleChecked);
+					KeyBindings.Add (KeyCode.Space, Command.ToggleChecked);
 				} else {
 				} else {
-					ClearKeyBinding (Key.Space);
+					KeyBindings.Remove (KeyCode.Space);
 				}
 				}
 
 
 				SetNeedsDisplay ();
 				SetNeedsDisplay ();
@@ -330,22 +330,22 @@ namespace Terminal.Gui {
 			AddCommand (Command.ToggleChecked, () => MarkUnmarkRow ());
 			AddCommand (Command.ToggleChecked, () => MarkUnmarkRow ());
 
 
 			// Default keybindings for all ListViews
 			// 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/>
 		///<inheritdoc/>
@@ -416,20 +416,11 @@ namespace Terminal.Gui {
 		public CollectionNavigator KeystrokeNavigator { get; private set; } = new CollectionNavigator ();
 		public CollectionNavigator KeystrokeNavigator { get; private set; } = new CollectionNavigator ();
 
 
 		///<inheritdoc/>
 		///<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
 			// 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) {
 				if (newItem is int && newItem != -1) {
 					SelectedItem = (int)newItem;
 					SelectedItem = (int)newItem;
 					EnsureSelectedItemVisible ();
 					EnsureSelectedItemVisible ();
@@ -437,7 +428,6 @@ namespace Terminal.Gui {
 					return true;
 					return true;
 				}
 				}
 			}
 			}
-
 			return false;
 			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.Collections.Generic;
 using System.Linq;
 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>
 	/// <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>
 	/// </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;
 					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) {
 			switch (DisplayMode) {
 			case DisplayModeLayout.Vertical:
 			case DisplayModeLayout.Vertical:
-				Move (0, cursor);
+				Move (0, i);
 				break;
 				break;
 			case DisplayModeLayout.Horizontal:
 			case DisplayModeLayout.Horizontal:
-				Move (horizontal [cursor].pos, 0);
+				Move (_horizontal [i].pos, 0);
 				break;
 				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>
 	/// <summary>
-	/// Used for choose the display mode of this <see cref="RadioGroup"/>
+	/// Vertical mode display. It's the default.
 	/// </summary>
 	/// </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));
 		AddCommand (Command.RightEnd, () => ScrollRight (_contentSize.Width));
 
 
 		// Default keybindings for this view
 		// 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) => {
 		Initialized += (s, e) => {
 			if (!_vertical.IsInitialized) {
 			if (!_vertical.IsInitialized) {
@@ -563,12 +563,12 @@ public class ScrollView : View {
 	}
 	}
 
 
 	///<inheritdoc/>
 	///<inheritdoc/>
-	public override bool ProcessKey (KeyEvent kb)
+	public override bool OnKeyDown (Key a)
 	{
 	{
-		if (base.ProcessKey (kb))
+		if (base.OnKeyDown (a))
 			return true;
 			return true;
 
 
-		var result = InvokeKeybindings (kb);
+		var result = InvokeKeyBindings (a);
 		if (result != null)
 		if (result != null)
 			return (bool)result;
 			return (bool)result;
 
 

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

@@ -1403,48 +1403,34 @@ public class Slider<T> : View {
 	void SetKeyBindings ()
 	void SetKeyBindings ()
 	{
 	{
 		if (_config._sliderOrientation == Orientation.Horizontal) {
 		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 {
 		} 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]);
 	Dictionary<int, SliderOption<T>> GetSetOptionDictionary () => _setOptions.ToDictionary (e => e, e => _options [e]);
 
 
 	void SetFocusedOption ()
 	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;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Text;
 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>
 	/// <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 ~. 
 	/// 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
 	/// 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"/>.
 	/// *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>
 	/// <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>
 	/// </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;
 			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; });
 			AddCommand (Command.RightEnd, () => { SelectedTab = Tabs.LastOrDefault (); return true; });
 
 
 			// Default keybindings for this view
 			// 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>
 		/// <summary>
@@ -229,18 +229,6 @@ namespace Terminal.Gui {
 			SelectedTabChanged?.Invoke (this, new TabChangedEventArgs (oldTab, newTab));
 			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>
 		/// <summary>
 		/// Changes the <see cref="SelectedTab"/> by the given <paramref name="amount"/>.  
 		/// Changes the <see cref="SelectedTab"/> by the given <paramref name="amount"/>.  
 		/// Positive for right, negative for left.  If no tab is currently selected then
 		/// 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.Wrapping = toWrap;
 			this.tableView = tableView;
 			this.tableView = tableView;
 
 
-			tableView.AddKeyBinding (Key.Space, Command.ToggleChecked);
+			tableView.KeyBindings.Add (KeyCode.Space, Command.ToggleChecked);
 
 
 			tableView.MouseClick += TableView_MouseClick;
 			tableView.MouseClick += TableView_MouseClick;
 			tableView.CellToggled += TableView_CellToggled;
 			tableView.CellToggled += TableView_CellToggled;

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

@@ -1,76 +1,75 @@
 using System;
 using System;
 using System.Linq;
 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>
 	/// <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>
 	/// </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"/> 
 /// 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)
 /// 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>
 /// </summary>
 public class ColumnStyle {
 public class ColumnStyle {
 
 

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

@@ -5,7 +5,7 @@ namespace Terminal.Gui;
 /// <summary>
 /// <summary>
 /// Defines rendering options that affect how the table is displayed.
 /// 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>
 /// </summary>
 public class TableStyle {
 public class TableStyle {
 
 

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

@@ -24,7 +24,7 @@ namespace Terminal.Gui {
 	/// <summary>
 	/// <summary>
 	/// View for tabular data based on a <see cref="ITableSource"/>.
 	/// 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>
 	/// </summary>
 	public class TableView : View {
 	public class TableView : View {
 
 
@@ -34,7 +34,8 @@ namespace Terminal.Gui {
 		private int selectedColumn;
 		private int selectedColumn;
 		private ITableSource table;
 		private ITableSource table;
 		private TableStyle style = new TableStyle ();
 		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? scrollLeftPoint;
 		Point? scrollRightPoint;
 		Point? scrollRightPoint;
@@ -169,18 +170,19 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public event EventHandler<CellToggledEventArgs> CellToggled;
 		public event EventHandler<CellToggledEventArgs> CellToggled;
 
 
+		// TODO: Update to use Key instead of KeyCode
 		/// <summary>
 		/// <summary>
 		/// The key which when pressed should trigger <see cref="CellActivated"/> event.  Defaults to Enter.
 		/// The key which when pressed should trigger <see cref="CellActivated"/> event.  Defaults to Enter.
 		/// </summary>
 		/// </summary>
-		public Key CellActivationKey {
+		public KeyCode CellActivationKey {
 			get => cellActivationKey;
 			get => cellActivationKey;
 			set {
 			set {
 				if (cellActivationKey != value) {
 				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
 					// 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;
 					cellActivationKey = value;
 				}
 				}
 			}
 			}
@@ -239,30 +241,30 @@ namespace Terminal.Gui {
 			AddCommand (Command.ToggleChecked, () => { ToggleCurrentCellSelection (); return true; });
 			AddCommand (Command.ToggleChecked, () => { ToggleCurrentCellSelection (); return true; });
 
 
 			// Default keybindings for this view
 			// 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/>
 		///<inheritdoc/>
@@ -758,36 +760,29 @@ namespace Terminal.Gui {
 			return new string (representation.TakeWhile (c => (availableHorizontalSpace -= ((Rune)c).GetColumns ()) > 0).ToArray ());
 			return new string (representation.TakeWhile (c => (availableHorizontalSpace -= ((Rune)c).GetColumns ()) > 0).ToArray ());
 		}
 		}
 
 
-
-
 		/// <inheritdoc/>
 		/// <inheritdoc/>
-		public override bool ProcessKey (KeyEvent keyEvent)
+		public override bool OnProcessKeyDown (Key keyEvent)
 		{
 		{
 			if (TableIsNullOrInvisible ()) {
 			if (TableIsNullOrInvisible ()) {
 				PositionCursor ();
 				PositionCursor ();
 				return false;
 				return false;
 			}
 			}
 
 
-			var result = InvokeKeybindings (keyEvent);
-			if (result != null) {
-				PositionCursor ();
-				return true;
-			}
-
+		
 			if (CollectionNavigator != null &&
 			if (CollectionNavigator != null &&
 				this.HasFocus &&
 				this.HasFocus &&
 				Table.Rows != 0 &&
 				Table.Rows != 0 &&
 				Terminal.Gui.CollectionNavigator.IsCompatibleKey (keyEvent) &&
 				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 CycleToNextTableEntryBeginningWith (keyEvent);
 			}
 			}
 
 
 			return false;
 			return false;
 		}
 		}
 
 
-		private bool CycleToNextTableEntryBeginningWith (KeyEvent keyEvent)
+		private bool CycleToNextTableEntryBeginningWith (Key keyEvent)
 		{
 		{
 			var row = SelectedRow;
 			var row = SelectedRow;
 
 
@@ -796,7 +791,7 @@ namespace Terminal.Gui {
 				return false;
 				return false;
 			}
 			}
 
 
-			int match = CollectionNavigator.GetNextMatchingItem (row, (char)keyEvent.KeyValue);
+			int match = CollectionNavigator.GetNextMatchingItem (row, (char)keyEvent);
 
 
 			if (match != -1) {
 			if (match != -1) {
 				SelectedRow = match;
 				SelectedRow = match;
@@ -1291,7 +1286,12 @@ namespace Terminal.Gui {
 		{
 		{
 			return ScreenToCell (clientX, clientY, out _, out _);
 			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="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="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>
 		/// <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 _);
 			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="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="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>
 		/// <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;
 		_tableView = table;
 		_tree = tree;
 		_tree = tree;
-		_tableView.KeyPressed += Table_KeyPress;
+		_tableView.KeyDown += Table_KeyPress;
 		_tableView.MouseClick += Table_MouseClick;
 		_tableView.MouseClick += Table_MouseClick;
 
 
 		var colList = subsequentColumns.Keys.ToList ();
 		var colList = subsequentColumns.Keys.ToList ();
@@ -68,7 +68,7 @@ public class TreeTableSource<T> : IEnumerableTableSource<T>, IDisposable where T
 	/// <inheritdoc/>
 	/// <inheritdoc/>
 	public void Dispose ()
 	public void Dispose ()
 	{
 	{
-		_tableView.KeyPressed -= Table_KeyPress;
+		_tableView.KeyDown -= Table_KeyPress;
 		_tableView.MouseClick -= Table_MouseClick;
 		_tableView.MouseClick -= Table_MouseClick;
 		_tree.Dispose ();
 		_tree.Dispose ();
 	}
 	}
@@ -106,7 +106,7 @@ public class TreeTableSource<T> : IEnumerableTableSource<T>, IDisposable where T
 		return sb.ToString ();
 		return sb.ToString ();
 	}
 	}
 
 
-	private void Table_KeyPress (object sender, KeyEventEventArgs e)
+	private void Table_KeyPress (object sender, Key e)
 	{
 	{
 		if (!IsInTreeColumn (_tableView.SelectedColumn, true)) {
 		if (!IsInTreeColumn (_tableView.SelectedColumn, true)) {
 			return;
 			return;
@@ -118,13 +118,13 @@ public class TreeTableSource<T> : IEnumerableTableSource<T>, IDisposable where T
 			return;
 			return;
 		}
 		}
 
 
-		if (e.KeyEvent.Key == Key.CursorLeft) {
+		if (e.KeyCode == KeyCode.CursorLeft) {
 			if (_tree.IsExpanded (obj)) {
 			if (_tree.IsExpanded (obj)) {
 				_tree.Collapse (obj);
 				_tree.Collapse (obj);
 				e.Handled = true;
 				e.Handled = true;
 			}
 			}
 		}
 		}
-		if (e.KeyEvent.Key == Key.CursorRight) {
+		if (e.KeyCode == KeyCode.CursorRight) {
 			if (_tree.CanExpand (obj) && !_tree.IsExpanded (obj)) {
 			if (_tree.CanExpand (obj) && !_tree.IsExpanded (obj)) {
 				_tree.Expand (obj);
 				_tree.Expand (obj);
 				e.Handled = true;
 				e.Handled = true;

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

@@ -13,7 +13,6 @@ using System.Threading;
 using System.Text;
 using System.Text;
 using Terminal.Gui.Resources;
 using Terminal.Gui.Resources;
 
 
-
 namespace Terminal.Gui {
 namespace Terminal.Gui {
 	/// <summary>
 	/// <summary>
 	///   Single-line text entry <see cref="View"/>
 	///   Single-line text entry <see cref="View"/>
@@ -23,7 +22,7 @@ namespace Terminal.Gui {
 	/// </remarks>
 	/// </remarks>
 	public class TextField : View {
 	public class TextField : View {
 		List<Rune> _text;
 		List<Rune> _text;
-		int _first, _point;
+		int _first, _cursorPosition;
 		int _selectedStart = -1; // -1 represents there is no text selection.
 		int _selectedStart = -1; // -1 represents there is no text selection.
 		string _selectedText;
 		string _selectedText;
 		HistoryText _historyText = new HistoryText ();
 		HistoryText _historyText = new HistoryText ();
@@ -58,14 +57,12 @@ namespace Terminal.Gui {
 		public event EventHandler<TextChangingEventArgs> TextChanging;
 		public event EventHandler<TextChangingEventArgs> TextChanging;
 
 
 		/// <summary>
 		/// <summary>
-		///   Changed event, raised when the text has changed.
-		/// </summary>
+		/// Changed event, raised when the text has changed.
 		/// <remarks>
 		/// <remarks>
 		///   This event is raised when the <see cref="Text"/> changes. 
 		///   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. 
 		///   The passed <see cref="EventArgs"/> is a <see cref="string"/> containing the old value. 
 		/// </remarks>
 		/// </remarks>
+		/// </summary>
 		public event EventHandler<TextChangedEventArgs> TextChanged;
 		public event EventHandler<TextChangedEventArgs> TextChanged;
 
 
 		/// <summary>
 		/// <summary>
@@ -102,8 +99,8 @@ namespace Terminal.Gui {
 				text = "";
 				text = "";
 
 
 			this._text = text.Split ("\n") [0].EnumerateRunes ().ToList ();
 			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;
 			CanFocus = true;
 			Used = true;
 			Used = true;
 			WantMousePositionReports = true;
 			WantMousePositionReports = true;
@@ -115,7 +112,7 @@ namespace Terminal.Gui {
 
 
 			// Things this view knows how to do
 			// Things this view knows how to do
 			AddCommand (Command.DeleteCharRight, () => { DeleteCharRight (); return true; });
 			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.LeftHomeExtend, () => { MoveHomeExtend (); return true; });
 			AddCommand (Command.RightEndExtend, () => { MoveEndExtend (); return true; });
 			AddCommand (Command.RightEndExtend, () => { MoveEndExtend (); return true; });
 			AddCommand (Command.LeftHome, () => { MoveHome (); return true; });
 			AddCommand (Command.LeftHome, () => { MoveHome (); return true; });
@@ -142,102 +139,103 @@ namespace Terminal.Gui {
 			AddCommand (Command.Paste, () => { Paste (); return true; });
 			AddCommand (Command.Paste, () => { Paste (); return true; });
 			AddCommand (Command.SelectAll, () => { SelectAll (); return true; });
 			AddCommand (Command.SelectAll, () => { SelectAll (); return true; });
 			AddCommand (Command.DeleteAll, () => { DeleteAll (); 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
 			// 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;
 			_currentCulture = Thread.CurrentThread.CurrentUICulture;
 
 
 			ContextMenu = new ContextMenu (this, BuildContextMenuBarItem ());
 			ContextMenu = new ContextMenu (this, BuildContextMenuBarItem ());
 			ContextMenu.KeyChanged += ContextMenu_KeyChanged;
 			ContextMenu.KeyChanged += ContextMenu_KeyChanged;
 
 
-			AddKeyBinding (ContextMenu.Key, Command.Accept);
+			KeyBindings.Add (ContextMenu.Key.KeyCode, KeyBindingScope.HotKey, Command.ShowContextMenu);
 		}
 		}
 
 
 		private MenuBarItem BuildContextMenuBarItem ()
 		private MenuBarItem BuildContextMenuBarItem ()
 		{
 		{
 			return new MenuBarItem (new MenuItem [] {
 			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)
 		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)
 		private void HistoryText_ChangeText (object sender, HistoryText.HistoryTextItem obj)
@@ -300,8 +298,6 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// <summary>
 		///   Sets or gets the text held by the view.
 		///   Sets or gets the text held by the view.
 		/// </summary>
 		/// </summary>
-		/// <remarks>
-		/// </remarks>
 		public new string Text {
 		public new string Text {
 			get {
 			get {
 				return StringExtensions.ToString (_text);
 				return StringExtensions.ToString (_text);
@@ -315,8 +311,8 @@ namespace Terminal.Gui {
 
 
 				var newText = OnTextChanging (value.Replace ("\t", "").Split ("\n") [0]);
 				var newText = OnTextChanging (value.Replace ("\t", "").Split ("\n") [0]);
 				if (newText.Cancel) {
 				if (newText.Cancel) {
-					if (_point > _text.Count) {
-						_point = _text.Count;
+					if (_cursorPosition > _text.Count) {
+						_cursorPosition = _text.Count;
 					}
 					}
 					return;
 					return;
 				}
 				}
@@ -325,8 +321,8 @@ namespace Terminal.Gui {
 
 
 				if (!Secret && !_historyText.IsFromHistory) {
 				if (!Secret && !_historyText.IsFromHistory) {
 					_historyText.Add (new List<List<RuneCell>> () { TextModel.ToRuneCellList (oldText) },
 					_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);
 						, HistoryText.LineStatus.Replaced);
 				}
 				}
 
 
@@ -334,8 +330,8 @@ namespace Terminal.Gui {
 
 
 				ProcessAutocomplete ();
 				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 ();
 				Adjust ();
@@ -345,26 +341,26 @@ namespace Terminal.Gui {
 
 
 		/// <summary>
 		/// <summary>
 		///   Sets the secret property.
 		///   Sets the secret property.
-		/// </summary>
 		/// <remarks>
 		/// <remarks>
 		///   This makes the text entry suitable for entering passwords.
 		///   This makes the text entry suitable for entering passwords.
 		/// </remarks>
 		/// </remarks>
+		/// </summary>
 		public bool Secret { get; set; }
 		public bool Secret { get; set; }
 
 
 		/// <summary>
 		/// <summary>
 		///    Sets or gets the current cursor position.
 		///    Sets or gets the current cursor position.
 		/// </summary>
 		/// </summary>
 		public virtual int CursorPosition {
 		public virtual int CursorPosition {
-			get { return _point; }
+			get { return _cursorPosition; }
 			set {
 			set {
 				if (value < 0) {
 				if (value < 0) {
-					_point = 0;
+					_cursorPosition = 0;
 				} else if (value > _text.Count) {
 				} else if (value > _text.Count) {
-					_point = _text.Count;
+					_cursorPosition = _text.Count;
 				} else {
 				} else {
-					_point = value;
+					_cursorPosition = value;
 				}
 				}
-				PrepareSelection (_selectedStart, _point - _selectedStart);
+				PrepareSelection (_selectedStart, _cursorPosition - _selectedStart);
 			}
 			}
 		}
 		}
 
 
@@ -399,12 +395,12 @@ namespace Terminal.Gui {
 
 
 			var col = 0;
 			var col = 0;
 			for (int idx = _first < 0 ? 0 : _first; idx < _text.Count; idx++) {
 			for (int idx = _first < 0 ? 0 : _first; idx < _text.Count; idx++) {
-				if (idx == _point)
+				if (idx == _cursorPosition)
 					break;
 					break;
 				var cols = _text [idx].GetColumns ();
 				var cols = _text [idx].GetColumns ();
 				TextModel.SetCol (ref col, Frame.Width - 1, cols);
 				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 offB = OffSetBackground ();
 			var containerFrame = SuperView?.BoundsToScreen (SuperView.Bounds) ?? default;
 			var containerFrame = SuperView?.BoundsToScreen (SuperView.Bounds) ?? default;
 			var thisFrame = BoundsToScreen (Bounds);
 			var thisFrame = BoundsToScreen (Bounds);
@@ -462,7 +458,7 @@ namespace Terminal.Gui {
 			for (int idx = p; idx < tcount; idx++) {
 			for (int idx = p; idx < tcount; idx++) {
 				var rune = _text [idx];
 				var rune = _text [idx];
 				var cols = rune.GetColumns ();
 				var cols = rune.GetColumns ();
-				if (idx == _point && HasFocus && !Used && _length == 0 && !ReadOnly) {
+				if (idx == _cursorPosition && HasFocus && !Used && _length == 0 && !ReadOnly) {
 					Driver.SetAttribute (selColor);
 					Driver.SetAttribute (selColor);
 				} else if (ReadOnly) {
 				} else if (ReadOnly) {
 					Driver.SetAttribute (idx >= _start && _length > 0 && idx < _start + _length ? selColor : roc);
 					Driver.SetAttribute (idx >= _start && _length > 0 && idx < _start + _length ? selColor : roc);
@@ -563,19 +559,20 @@ namespace Terminal.Gui {
 
 
 		void Adjust ()
 		void Adjust ()
 		{
 		{
-			if (!IsAdded)
+			if (!IsAdded) {
 				return;
 				return;
+			}
 
 
 			int offB = OffSetBackground ();
 			int offB = OffSetBackground ();
 			bool need = NeedsDisplay || !Used;
 			bool need = NeedsDisplay || !Used;
-			if (_point < _first) {
-				_first = _point;
+			if (_cursorPosition < _first) {
+				_first = _cursorPosition;
 				need = true;
 				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,
 				_first = Math.Max (TextModel.CalculateLeftColumn (_text, _first,
-					_point, Frame.Width + offB), 0);
+					_cursorPosition, Frame.Width + offB), 0);
 				need = true;
 				need = true;
 			}
 			}
 			if (need) {
 			if (need) {
@@ -617,13 +614,21 @@ namespace Terminal.Gui {
 				Clipboard.Contents = StringExtensions.ToString (text.ToList ());
 				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>
 		/// <summary>
 		/// Processes key presses for the <see cref="TextField"/>.
 		/// Processes key presses for the <see cref="TextField"/>.
-		/// </summary>
-		/// <param name="kb"></param>
-		/// <returns></returns>
 		/// <remarks>
 		/// <remarks>
 		/// The <see cref="TextField"/> control responds to the following keys:
 		/// The <see cref="TextField"/> control responds to the following keys:
 		/// <list type="table">
 		/// <list type="table">
@@ -637,61 +642,56 @@ namespace Terminal.Gui {
 		///    </item>
 		///    </item>
 		/// </list>
 		/// </list>
 		/// </remarks>
 		/// </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
 			// 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.
 			// 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;
 				return false;
+			}
 
 
-			if (ReadOnly)
+			if (ReadOnly) {
 				return true;
 				return true;
+			}
 
 
-			InsertText (kb);
+			InsertText (a, true);
 
 
 			return 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;
 			List<Rune> newText = _text;
 			if (_length > 0) {
 			if (_length > 0) {
 				newText = DeleteSelectedText ();
 				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) {
 			if (Used) {
-				_point++;
-				if (_point == newText.Count + 1) {
+				_cursorPosition++;
+				if (_cursorPosition == newText.Count + 1) {
 					SetText (newText.Concat (kbstr).ToList ());
 					SetText (newText.Concat (kbstr).ToList ());
 				} else {
 				} 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 {
 			} 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 ();
 			Adjust ();
 		}
 		}
@@ -715,11 +715,11 @@ namespace Terminal.Gui {
 		public virtual void KillWordBackwards ()
 		public virtual void KillWordBackwards ()
 		{
 		{
 			ClearAllSelection ();
 			ClearAllSelection ();
-			var newPos = GetModel ().WordBackward (_point, 0);
+			var newPos = GetModel ().WordBackward (_cursorPosition, 0);
 			if (newPos == null) return;
 			if (newPos == null) return;
 			if (newPos.Value.col != -1) {
 			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 ();
 			Adjust ();
 		}
 		}
@@ -730,10 +730,10 @@ namespace Terminal.Gui {
 		public virtual void KillWordForwards ()
 		public virtual void KillWordForwards ()
 		{
 		{
 			ClearAllSelection ();
 			ClearAllSelection ();
-			var newPos = GetModel ().WordForward (_point, 0);
+			var newPos = GetModel ().WordForward (_cursorPosition, 0);
 			if (newPos == null) return;
 			if (newPos == null) return;
 			if (newPos.Value.col != -1) {
 			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 ();
 			Adjust ();
 		}
 		}
@@ -741,20 +741,20 @@ namespace Terminal.Gui {
 		void MoveWordRight ()
 		void MoveWordRight ()
 		{
 		{
 			ClearAllSelection ();
 			ClearAllSelection ();
-			var newPos = GetModel ().WordForward (_point, 0);
+			var newPos = GetModel ().WordForward (_cursorPosition, 0);
 			if (newPos == null) return;
 			if (newPos == null) return;
 			if (newPos.Value.col != -1)
 			if (newPos.Value.col != -1)
-				_point = newPos.Value.col;
+				_cursorPosition = newPos.Value.col;
 			Adjust ();
 			Adjust ();
 		}
 		}
 
 
 		void MoveWordLeft ()
 		void MoveWordLeft ()
 		{
 		{
 			ClearAllSelection ();
 			ClearAllSelection ();
-			var newPos = GetModel ().WordBackward (_point, 0);
+			var newPos = GetModel ().WordBackward (_cursorPosition, 0);
 			if (newPos == null) return;
 			if (newPos == null) return;
 			if (newPos.Value.col != -1)
 			if (newPos.Value.col != -1)
-				_point = newPos.Value.col;
+				_cursorPosition = newPos.Value.col;
 			Adjust ();
 			Adjust ();
 		}
 		}
 
 
@@ -803,11 +803,11 @@ namespace Terminal.Gui {
 				return;
 				return;
 
 
 			ClearAllSelection ();
 			ClearAllSelection ();
-			if (_point == 0)
+			if (_cursorPosition == 0)
 				return;
 				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 ();
 			Adjust ();
 		}
 		}
 
 
@@ -817,19 +817,19 @@ namespace Terminal.Gui {
 				return;
 				return;
 
 
 			ClearAllSelection ();
 			ClearAllSelection ();
-			if (_point >= _text.Count)
+			if (_cursorPosition >= _text.Count)
 				return;
 				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 ();
 			Adjust ();
 		}
 		}
 
 
 		void MoveRight ()
 		void MoveRight ()
 		{
 		{
 			ClearAllSelection ();
 			ClearAllSelection ();
-			if (_point == _text.Count)
+			if (_cursorPosition == _text.Count)
 				return;
 				return;
-			_point++;
+			_cursorPosition++;
 			Adjust ();
 			Adjust ();
 		}
 		}
 
 
@@ -839,40 +839,40 @@ namespace Terminal.Gui {
 		public void MoveEnd ()
 		public void MoveEnd ()
 		{
 		{
 			ClearAllSelection ();
 			ClearAllSelection ();
-			_point = _text.Count;
+			_cursorPosition = _text.Count;
 			Adjust ();
 			Adjust ();
 		}
 		}
 
 
 		void MoveLeft ()
 		void MoveLeft ()
 		{
 		{
 			ClearAllSelection ();
 			ClearAllSelection ();
-			if (_point > 0) {
-				_point--;
+			if (_cursorPosition > 0) {
+				_cursorPosition--;
 				Adjust ();
 				Adjust ();
 			}
 			}
 		}
 		}
 
 
 		void MoveWordRightExtend ()
 		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);
 				var newPos = GetModel ().WordForward (x, 0);
 				if (newPos == null) return;
 				if (newPos == null) return;
 				if (newPos.Value.col != -1)
 				if (newPos.Value.col != -1)
-					_point = newPos.Value.col;
+					_cursorPosition = newPos.Value.col;
 				PrepareSelection (x, newPos.Value.col - x);
 				PrepareSelection (x, newPos.Value.col - x);
 			}
 			}
 		}
 		}
 
 
 		void MoveWordLeftExtend ()
 		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) {
 				if (x > 0) {
 					var newPos = GetModel ().WordBackward (x, 0);
 					var newPos = GetModel ().WordBackward (x, 0);
 					if (newPos == null) return;
 					if (newPos == null) return;
 					if (newPos.Value.col != -1)
 					if (newPos.Value.col != -1)
-						_point = newPos.Value.col;
+						_cursorPosition = newPos.Value.col;
 					PrepareSelection (x, newPos.Value.col - x);
 					PrepareSelection (x, newPos.Value.col - x);
 				}
 				}
 			}
 			}
@@ -880,65 +880,68 @@ namespace Terminal.Gui {
 
 
 		void MoveRightExtend ()
 		void MoveRightExtend ()
 		{
 		{
-			if (_point < _text.Count) {
-				PrepareSelection (_point++, 1);
+			if (_cursorPosition < _text.Count) {
+				PrepareSelection (_cursorPosition++, 1);
 			}
 			}
 		}
 		}
 
 
 		void MoveLeftExtend ()
 		void MoveLeftExtend ()
 		{
 		{
-			if (_point > 0) {
-				PrepareSelection (_point--, -1);
+			if (_cursorPosition > 0) {
+				PrepareSelection (_cursorPosition--, -1);
 			}
 			}
 		}
 		}
 
 
 		void MoveHome ()
 		void MoveHome ()
 		{
 		{
 			ClearAllSelection ();
 			ClearAllSelection ();
-			_point = 0;
+			_cursorPosition = 0;
 			Adjust ();
 			Adjust ();
 		}
 		}
 
 
 		void MoveEndExtend ()
 		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 ()
 		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>
 		/// <summary>
-		/// Deletes the left character.
+		/// Deletes the character to the left.
 		/// </summary>
 		/// </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)
 			if (ReadOnly)
 				return;
 				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 (_length == 0) {
-				if (_point == 0)
+				if (_cursorPosition == 0)
 					return;
 					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 {
 				} else {
-					SetText (_text.GetRange (0, _oldCursorPos - 1));
+					SetText (_text.GetRange (0, _preTextChangedCursorPos - 1));
 				}
 				}
 				Adjust ();
 				Adjust ();
 			} else {
 			} else {
@@ -949,20 +952,20 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// Deletes the right character.
+		/// Deletes the character to the right.
 		/// </summary>
 		/// </summary>
 		public virtual void DeleteCharRight ()
 		public virtual void DeleteCharRight ()
 		{
 		{
 			if (ReadOnly)
 			if (ReadOnly)
 				return;
 				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 (_length == 0) {
-				if (_text.Count == 0 || _text.Count == _point)
+				if (_text.Count == 0 || _text.Count == _cursorPosition)
 					return;
 					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 ();
 				Adjust ();
 			} else {
 			} else {
 				var newText = DeleteSelectedText ();
 				var newText = DeleteSelectedText ();
@@ -1007,7 +1010,7 @@ namespace Terminal.Gui {
 
 
 			_selectedStart = 0;
 			_selectedStart = 0;
 			MoveEndExtend ();
 			MoveEndExtend ();
-			DeleteCharLeft ();
+			DeleteCharLeft (false);
 			SetNeedsDisplay ();
 			SetNeedsDisplay ();
 		}
 		}
 
 
@@ -1024,7 +1027,7 @@ namespace Terminal.Gui {
 				} else {
 				} else {
 					_selectedStart = value;
 					_selectedStart = value;
 				}
 				}
-				PrepareSelection (_selectedStart, _point - _selectedStart);
+				PrepareSelection (_selectedStart, _cursorPosition - _selectedStart);
 			}
 			}
 		}
 		}
 
 
@@ -1105,7 +1108,7 @@ namespace Terminal.Gui {
 				if (newPosFw == null) return true;
 				if (newPosFw == null) return true;
 				ClearAllSelection ();
 				ClearAllSelection ();
 				if (newPosFw.Value.col != -1 && sbw != -1) {
 				if (newPosFw.Value.col != -1 && sbw != -1) {
-					_point = newPosFw.Value.col;
+					_cursorPosition = newPosFw.Value.col;
 				}
 				}
 				PrepareSelection (sbw, newPosFw.Value.col - sbw);
 				PrepareSelection (sbw, newPosFw.Value.col - sbw);
 			} else if (ev.Flags == MouseFlags.Button1TripleClicked) {
 			} else if (ev.Flags == MouseFlags.Button1TripleClicked) {
@@ -1148,14 +1151,14 @@ namespace Terminal.Gui {
 				pX = TextModel.GetColFromX (_text, _first, x);
 				pX = TextModel.GetColFromX (_text, _first, x);
 			}
 			}
 			if (_first + pX > _text.Count) {
 			if (_first + pX > _text.Count) {
-				_point = _text.Count;
+				_cursorPosition = _text.Count;
 			} else if (_first + pX < _first) {
 			} else if (_first + pX < _first) {
-				_point = 0;
+				_cursorPosition = 0;
 			} else {
 			} else {
-				_point = _first + pX;
+				_cursorPosition = _first + pX;
 			}
 			}
 
 
-			return _point;
+			return _cursorPosition;
 		}
 		}
 
 
 		void PrepareSelection (int x, int direction = 0)
 		void PrepareSelection (int x, int direction = 0)
@@ -1174,6 +1177,7 @@ namespace Terminal.Gui {
 				} else if (_start > -1 && _length == 0) {
 				} else if (_start > -1 && _length == 0) {
 					_selectedText = null;
 					_selectedText = null;
 				}
 				}
+				SetNeedsDisplay ();
 			} else if (_length > 0 || _selectedText != null) {
 			} else if (_length > 0 || _selectedText != null) {
 				ClearAllSelection ();
 				ClearAllSelection ();
 			}
 			}
@@ -1199,8 +1203,8 @@ namespace Terminal.Gui {
 
 
 		void SetSelectedStartSelectedLength ()
 		void SetSelectedStartSelectedLength ()
 		{
 		{
-			if (SelectedStart > -1 && _point < SelectedStart) {
-				_start = _point;
+			if (SelectedStart > -1 && _cursorPosition < SelectedStart) {
+				_start = _cursorPosition;
 			} else {
 			} else {
 				_start = SelectedStart;
 				_start = SelectedStart;
 			}
 			}
@@ -1234,12 +1238,12 @@ namespace Terminal.Gui {
 		List<Rune> DeleteSelectedText ()
 		List<Rune> DeleteSelectedText ()
 		{
 		{
 			SetSelectedStartSelectedLength ();
 			SetSelectedStartSelectedLength ();
-			int selStart = SelectedStart > -1 ? _start : _point;
+			int selStart = SelectedStart > -1 ? _start : _cursorPosition;
 			var newText = StringExtensions.ToString (_text.GetRange (0, selStart)) +
 			var newText = StringExtensions.ToString (_text.GetRange (0, selStart)) +
 				 StringExtensions.ToString (_text.GetRange (selStart + _length, _text.Count - (selStart + _length)));
 				 StringExtensions.ToString (_text.GetRange (selStart + _length, _text.Count - (selStart + _length)));
 
 
 			ClearAllSelection ();
 			ClearAllSelection ();
-			_point = selStart >= newText.GetRuneCount () ? newText.GetRuneCount () : selStart;
+			_cursorPosition = selStart >= newText.GetRuneCount () ? newText.GetRuneCount () : selStart;
 			return newText.ToRuneList ();
 			return newText.ToRuneList ();
 		}
 		}
 
 
@@ -1259,7 +1263,7 @@ namespace Terminal.Gui {
 				cbTxt +
 				cbTxt +
 				StringExtensions.ToString (_text.GetRange (selStart + _length, _text.Count - (selStart + _length)));
 				StringExtensions.ToString (_text.GetRange (selStart + _length, _text.Count - (selStart + _length)));
 
 
-			_point = selStart + cbTxt.GetRuneCount ();
+			_cursorPosition = selStart + cbTxt.GetRuneCount ();
 			ClearAllSelection ();
 			ClearAllSelection ();
 			SetNeedsDisplay ();
 			SetNeedsDisplay ();
 			Adjust ();
 			Adjust ();
@@ -1298,21 +1302,21 @@ namespace Terminal.Gui {
 		/// exactly as if the user had just typed it
 		/// exactly as if the user had just typed it
 		/// </summary>
 		/// </summary>
 		/// <param name="toAdd">Text to add</param>
 		/// <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)
 		public void InsertText (string toAdd, bool useOldCursorPos = true)
 		{
 		{
 			foreach (var ch in toAdd) {
 			foreach (var ch in toAdd) {
 
 
-				Key key;
+				KeyCode key;
 
 
 				try {
 				try {
-					key = (Key)ch;
+					key = (KeyCode)ch;
 				} catch (Exception) {
 				} catch (Exception) {
 
 
 					throw new ArgumentException ($"Cannot insert character '{ch}' because it does not map to a Key");
 					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; });
 			AddCommand (Command.Right, () => { CursorRight (); return true; });
 
 
 			// Default keybindings for this view
 			// 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>
 		/// <summary>
@@ -612,20 +612,17 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		///<inheritdoc/>
 		///<inheritdoc/>
-		public override bool ProcessKey (KeyEvent kb)
+		public override bool OnProcessKeyDown (Key a)
 		{
 		{
 			if (provider == null) {
 			if (provider == null) {
 				return false;
 				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;
 				return false;
-
-			var key = new Rune ((uint)kb.KeyValue);
+			}
+			
+			var key = a.AsRune;
 
 
 			var inserted = provider.InsertAt ((char)key.Value, cursorPosition);
 			var inserted = provider.InsertAt ((char)key.Value, cursorPosition);
 
 
@@ -633,7 +630,7 @@ namespace Terminal.Gui {
 				CursorRight ();
 				CursorRight ();
 			}
 			}
 
 
-			return true;
+			return false;
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>

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

@@ -1673,126 +1673,127 @@ namespace Terminal.Gui {
 			});
 			});
 
 
 			// Default keybindings for this view
 			// 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;
 			_currentCulture = Thread.CurrentThread.CurrentUICulture;
 
 
 			ContextMenu = new ContextMenu () { MenuItems = BuildContextMenuBarItem () };
 			ContextMenu = new ContextMenu () { MenuItems = BuildContextMenuBarItem () };
 			ContextMenu.KeyChanged += ContextMenu_KeyChanged!;
 			ContextMenu.KeyChanged += ContextMenu_KeyChanged!;
 
 
-			AddKeyBinding (ContextMenu.Key, Command.Accept);
+			KeyBindings.Add ((KeyCode)ContextMenu.Key, KeyBindingScope.HotKey, Command.Accept);
 		}
 		}
 
 
 		private MenuBarItem BuildContextMenuBarItem ()
 		private MenuBarItem BuildContextMenuBarItem ()
 		{
 		{
 			return new MenuBarItem (new MenuItem [] {
 			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)
 		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)
 		private void Model_LinesLoaded (object sender, EventArgs e)
@@ -1866,12 +1867,12 @@ namespace Terminal.Gui {
 
 
 		void Top_AlternateBackwardKeyChanged (object sender, KeyChangedEventArgs e)
 		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)
 		void Top_AlternateForwardKeyChanged (object sender, KeyChangedEventArgs e)
 		{
 		{
-			ReplaceKeyBinding (e.OldKey, e.NewKey);
+			KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey);
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -2516,9 +2517,9 @@ namespace Terminal.Gui {
 		{
 		{
 			// BUGBUG: (v2 truecolor) This code depends on 8-bit color names; disabling for now
 			// BUGBUG: (v2 truecolor) This code depends on 8-bit color names; disabling for now
 			//if ((colorScheme!.HotNormal.Foreground & colorScheme.Focus.Background) == colorScheme.Focus.Foreground) {
 			//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 {
 			//} 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;
 				_selectionStartColumn = nStartCol;
 				_wrapNeeded = true;
 				_wrapNeeded = true;
 
 
-                SetNeedsDisplay();
-            }
+				SetNeedsDisplay ();
+			}
 			if (_currentCaller != null)
 			if (_currentCaller != null)
 				throw new InvalidOperationException ($"WordWrap settings was changed after the {_currentCaller} call.");
 				throw new InvalidOperationException ($"WordWrap settings was changed after the {_currentCaller} call.");
 		}
 		}
@@ -3058,16 +3059,16 @@ namespace Terminal.Gui {
 		{
 		{
 			foreach (var ch in toAdd) {
 			foreach (var ch in toAdd) {
 
 
-				Key key;
+				KeyCode key;
 
 
 				try {
 				try {
-					key = (Key)ch;
+					key = (KeyCode)ch;
 				} catch (Exception) {
 				} catch (Exception) {
 
 
 					throw new ArgumentException ($"Cannot insert character '{ch}' because it does not map to a Key");
 					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) {
 			if (NeedsDisplay) {
@@ -3403,28 +3404,32 @@ namespace Terminal.Gui {
 		bool _shiftSelecting;
 		bool _shiftSelecting;
 
 
 		///<inheritdoc/>
 		///<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 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;
 				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 ();
 			ResetColumnTrack ();
+
 			// Ignore control characters and other special keys
 			// 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;
 				return false;
+			}
 
 
-			InsertText (kb);
+			InsertText (a);
 			DoNeededAction ();
 			DoNeededAction ();
 
 
 			return true;
 			return true;
@@ -3793,7 +3798,7 @@ namespace Terminal.Gui {
 			if (!AllowsTab || _isReadOnly) {
 			if (!AllowsTab || _isReadOnly) {
 				return ProcessMoveNextView ();
 				return ProcessMoveNextView ();
 			}
 			}
-			InsertText (new KeyEvent ((Key)'\t', null));
+			InsertText (new Key ((KeyCode)'\t'));
 			DoNeededAction ();
 			DoNeededAction ();
 			return true;
 			return true;
 		}
 		}
@@ -4320,11 +4325,12 @@ namespace Terminal.Gui {
 			_continuousFind = false;
 			_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
 			//So that special keys like tab can be processed
-			if (_isReadOnly)
+			if (_isReadOnly) {
 				return true;
 				return true;
+			}
 
 
 			SetWrapModel ();
 			SetWrapModel ();
 
 
@@ -4333,22 +4339,22 @@ namespace Terminal.Gui {
 			if (_selecting) {
 			if (_selecting) {
 				ClearSelectedRegion ();
 				ClearSelectedRegion ();
 			}
 			}
-			if (kb.Key == Key.Enter) {
+			if (a.KeyCode == KeyCode.Enter) {
 				_model.AddLine (_currentRow + 1, new List<RuneCell> ());
 				_model.AddLine (_currentRow + 1, new List<RuneCell> ());
 				_currentRow++;
 				_currentRow++;
 				_currentColumn = 0;
 				_currentColumn = 0;
-			} else if ((uint)kb.Key == 13) {
+			} else if ((uint)a.KeyCode == '\r') {
 				_currentColumn = 0;
 				_currentColumn = 0;
 			} else {
 			} else {
 				if (Used) {
 				if (Used) {
-					Insert (new RuneCell { Rune = (Rune)(uint)kb.Key, ColorScheme = colorScheme });
+					Insert (new RuneCell { Rune = a.AsRune, ColorScheme = colorScheme });
 					_currentColumn++;
 					_currentColumn++;
 					if (_currentColumn >= _leftColumn + Frame.Width) {
 					if (_currentColumn >= _leftColumn + Frame.Width) {
 						_leftColumn++;
 						_leftColumn++;
 						SetNeedsDisplay ();
 						SetNeedsDisplay ();
 					}
 					}
 				} else {
 				} else {
-					Insert (new RuneCell { Rune = (Rune)(uint)kb.Key, ColorScheme = colorScheme });
+					Insert (new RuneCell { Rune = a.AsRune, ColorScheme = colorScheme });
 					_currentColumn++;
 					_currentColumn++;
 				}
 				}
 			}
 			}
@@ -4390,14 +4396,14 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		///<inheritdoc/>
 		///<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 true;
 			}
 			}
 
 
-			return false;
+			return base.OnKeyUp (a);
 		}
 		}
 
 
 		void DoNeededAction ()
 		void DoNeededAction ()

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

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

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

@@ -23,7 +23,7 @@ namespace Terminal.Gui {
 	/// </remarks>
 	/// </remarks>
 	public partial class Toplevel : View {
 	public partial class Toplevel : View {
 		/// <summary>
 		/// <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>
 		/// </summary>
 		/// <remarks>
 		/// <remarks>
 		///    Setting this property directly is discouraged. Use <see cref="Application.RequestStop"/> instead. 
 		///    Setting this property directly is discouraged. Use <see cref="Application.RequestStop"/> instead. 
@@ -38,7 +38,7 @@ namespace Terminal.Gui {
 		public event EventHandler Loaded;
 		public event EventHandler Loaded;
 
 
 		/// <summary>
 		/// <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.
 		/// Subscribe to this event to perform tasks when the <see cref="Toplevel"/> has been laid out and focus has been set.
 		/// changes. 
 		/// changes. 
 		/// <para>A Ready event handler is a good place to finalize initialization after calling 
 		/// <para>A Ready event handler is a good place to finalize initialization after calling 
@@ -138,7 +138,7 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// <summary>
 		/// Called from <see cref="Application.Begin(Toplevel)"/> before the <see cref="Toplevel"/> redraws for the first time. 
 		/// Called from <see cref="Application.Begin(Toplevel)"/> before the <see cref="Toplevel"/> redraws for the first time. 
 		/// </summary>
 		/// </summary>
-		virtual public void OnLoaded ()
+		public virtual void OnLoaded ()
 		{
 		{
 			IsLoaded = true;
 			IsLoaded = true;
 			foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) {
 			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.NextViewOrTop, () => { MoveNextViewOrTop (); return true; });
 			AddCommand (Command.PreviousViewOrTop, () => { MovePreviousViewOrTop (); return true; });
 			AddCommand (Command.PreviousViewOrTop, () => { MovePreviousViewOrTop (); return true; });
 			AddCommand (Command.Refresh, () => { Application.Refresh (); 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
 			// 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)
 		private void Application_UnGrabbingMouse (object sender, GrabMouseEventArgs e)
@@ -261,7 +272,7 @@ namespace Terminal.Gui {
 		/// <param name="e"></param>
 		/// <param name="e"></param>
 		public virtual void OnAlternateForwardKeyChanged (KeyChangedEventArgs e)
 		public virtual void OnAlternateForwardKeyChanged (KeyChangedEventArgs e)
 		{
 		{
-			ReplaceKeyBinding (e.OldKey, e.NewKey);
+			KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey);
 			AlternateForwardKeyChanged?.Invoke (this, e);
 			AlternateForwardKeyChanged?.Invoke (this, e);
 		}
 		}
 
 
@@ -276,7 +287,7 @@ namespace Terminal.Gui {
 		/// <param name="e"></param>
 		/// <param name="e"></param>
 		public virtual void OnAlternateBackwardKeyChanged (KeyChangedEventArgs e)
 		public virtual void OnAlternateBackwardKeyChanged (KeyChangedEventArgs e)
 		{
 		{
-			ReplaceKeyBinding (e.OldKey, e.NewKey);
+			KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey);
 			AlternateBackwardKeyChanged?.Invoke (this, e);
 			AlternateBackwardKeyChanged?.Invoke (this, e);
 		}
 		}
 
 
@@ -291,7 +302,7 @@ namespace Terminal.Gui {
 		/// <param name="e"></param>
 		/// <param name="e"></param>
 		public virtual void OnQuitKeyChanged (KeyChangedEventArgs e)
 		public virtual void OnQuitKeyChanged (KeyChangedEventArgs e)
 		{
 		{
-			ReplaceKeyBinding (e.OldKey, e.NewKey);
+			KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey);
 			QuitKeyChanged?.Invoke (this, e);
 			QuitKeyChanged?.Invoke (this, e);
 		}
 		}
 
 
@@ -318,7 +329,7 @@ namespace Terminal.Gui {
 		/// 
 		/// 
 		/// <list type="bullet">
 		/// <list type="bullet">
 		///   <item>
 		///   <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>
 		///   <item>
 		///   <item>
 		///		<description>The Toplevel will act as an embedded view (not a modal/pop-up).</description>
 		///		<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">
 		/// <list type="bullet">
 		///   <item>
 		///   <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>
 		///   <item>
 		///   <item>
 		///		<description>The Toplevel will and look like a modal (pop-up) (e.g. see <see cref="Dialog"/>.</description>
 		///		<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>
 		/// </summary>
 		public bool IsLoaded { get; private set; }
 		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 ()
 		private void MovePreviousViewOrTop ()
 		{
 		{
 			if (Application.OverlappedTop == null) {
 			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)
 		View GetDeepestFocusedSubview (View view)
 		{
 		{
 			if (view == null) {
 			if (view == null) {

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

@@ -195,6 +195,8 @@ namespace Terminal.Gui {
 				if (modelScheme != null) {
 				if (modelScheme != null) {
 					// use it
 					// use it
 					modelColor = isSelected ? modelScheme.Focus : modelScheme.Normal;
 					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>
 	/// <summary>
 	/// Interface for all non generic members of <see cref="TreeView{T}"/>.
 	/// 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>
 	/// </summary>
 	public interface ITreeView {
 	public interface ITreeView {
 		/// <summary>
 		/// <summary>
@@ -37,7 +37,7 @@ namespace Terminal.Gui {
 	/// Convenience implementation of generic <see cref="TreeView{T}"/> for any tree were all nodes
 	/// Convenience implementation of generic <see cref="TreeView{T}"/> for any tree were all nodes
 	/// implement <see cref="ITreeNode"/>.
 	/// 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>
 	/// </summary>
 	public class TreeView : TreeView<ITreeNode> {
 	public class TreeView : TreeView<ITreeNode> {
 
 
@@ -56,7 +56,7 @@ namespace Terminal.Gui {
 	/// Hierarchical tree view with expandable branches. Branch objects are dynamically determined
 	/// Hierarchical tree view with expandable branches. Branch objects are dynamically determined
 	/// when expanded using a user defined <see cref="ITreeBuilder{T}"/>.
 	/// 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>
 	/// </summary>
 	public class TreeView<T> : View, ITreeView where T : class {
 	public class TreeView<T> : View, ITreeView where T : class {
 		private int scrollOffsetVertical;
 		private int scrollOffsetVertical;
@@ -119,15 +119,16 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public event EventHandler<ObjectActivatedEventArgs<T>> ObjectActivated;
 		public event EventHandler<ObjectActivatedEventArgs<T>> ObjectActivated;
 
 
+		// TODO: Update to use Key instead of KeyCode
 		/// <summary>
 		/// <summary>
 		/// Key which when pressed triggers <see cref="TreeView{T}.ObjectActivated"/>.
 		/// Key which when pressed triggers <see cref="TreeView{T}.ObjectActivated"/>.
 		/// Defaults to Enter.
 		/// Defaults to Enter.
 		/// </summary>
 		/// </summary>
-		public Key ObjectActivationKey {
+		public KeyCode ObjectActivationKey {
 			get => objectActivationKey;
 			get => objectActivationKey;
 			set {
 			set {
 				if (objectActivationKey != value) {
 				if (objectActivationKey != value) {
-					ReplaceKeyBinding (ObjectActivationKey, value);
+					KeyBindings.Replace (ObjectActivationKey, value);
 					objectActivationKey = value;
 					objectActivationKey = value;
 				}
 				}
 			}
 			}
@@ -162,7 +163,7 @@ namespace Terminal.Gui {
 		/// (nodes added but no tree builder set).
 		/// (nodes added but no tree builder set).
 		/// </summary>
 		/// </summary>
 		public static string NoBuilderError = "ERROR: TreeBuilder Not Set";
 		public static string NoBuilderError = "ERROR: TreeBuilder Not Set";
-		private Key objectActivationKey = Key.Enter;
+		private KeyCode objectActivationKey = KeyCode.Enter;
 
 
 		/// <summary>
 		/// <summary>
 		/// Called when the <see cref="SelectedObject"/> changes.
 		/// Called when the <see cref="SelectedObject"/> changes.
@@ -286,27 +287,27 @@ namespace Terminal.Gui {
 			AddCommand (Command.Accept, () => { ActivateSelectedObjectIfAny (); return true; });
 			AddCommand (Command.Accept, () => { ActivateSelectedObjectIfAny (); return true; });
 
 
 			// Default keybindings for this view
 			// 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>
 		/// <summary>
@@ -621,19 +622,14 @@ namespace Terminal.Gui {
 		public CollectionNavigator KeystrokeNavigator { get; private set; } = new CollectionNavigator ();
 		public CollectionNavigator KeystrokeNavigator { get; private set; } = new CollectionNavigator ();
 
 
 		/// <inheritdoc/>
 		/// <inheritdoc/>
-		public override bool ProcessKey (KeyEvent keyEvent)
+		public override bool OnProcessKeyDown (Key keyEvent)
 		{
 		{
 			if (!Enabled) {
 			if (!Enabled) {
 				return false;
 				return false;
 			}
 			}
 
 
 			try {
 			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 not a keybinding, is the key a searchable key press?
 				if (CollectionNavigator.IsCompatibleKey (keyEvent) && AllowLetterBasedNavigation) {
 				if (CollectionNavigator.IsCompatibleKey (keyEvent) && AllowLetterBasedNavigation) {
 					IReadOnlyCollection<Branch<T>> map;
 					IReadOnlyCollection<Branch<T>> map;
@@ -644,7 +640,7 @@ namespace Terminal.Gui {
 
 
 					// Find the current selected object within the tree
 					// Find the current selected object within the tree
 					var current = map.IndexOf (b => b.Model == SelectedObject);
 					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) {
 					if (newIndex is int && newIndex != -1) {
 						SelectedObject = map.ElementAt ((int)newIndex).Model;
 						SelectedObject = map.ElementAt ((int)newIndex).Model;
@@ -654,10 +650,12 @@ namespace Terminal.Gui {
 					}
 					}
 				}
 				}
 			} finally {
 			} finally {
-				PositionCursor ();
+				if (IsInitialized) {
+					PositionCursor ();
+				}
 			}
 			}
 
 
-			return base.ProcessKey (keyEvent);
+			return false;
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>

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

@@ -4,532 +4,524 @@ using System.Linq;
 using System.Text;
 using System.Text;
 using Terminal.Gui.Resources;
 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>
 	/// <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>
 	/// </summary>
 	/// <remarks>
 	/// <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>
 	/// </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;
 				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;
 				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 {
 				} 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 {
 namespace UICatalog {
 
 
 	class KeyBindingsDialog : Dialog {
 	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 Command[] commands;
 		private ListView commandsListView;
 		private ListView commandsListView;
 		private Label keyLabel;
 		private Label keyLabel;
@@ -28,7 +28,7 @@ namespace UICatalog {
 			Dictionary<View, bool> knownViews = new Dictionary<View, bool> ();
 			Dictionary<View, bool> knownViews = new Dictionary<View, bool> ();
 
 
 			private object lockKnownViews = new object ();
 			private object lockKnownViews = new object ();
-			private Dictionary<Command, Key> keybindings;
+			private Dictionary<Command, KeyCode> keybindings;
 
 
 			public ViewTracker (View top)
 			public ViewTracker (View top)
 			{
 			{
@@ -70,7 +70,7 @@ namespace UICatalog {
 				Instance = new ViewTracker (Application.Top);
 				Instance = new ViewTracker (Application.Top);
 			}
 			}
 
 
-			internal void StartUsingNewKeyMap (Dictionary<Command, Key> currentBindings)
+			internal void StartUsingNewKeyMap (Dictionary<Command, KeyCode> currentBindings)
 			{
 			{
 				lock (lockKnownViews) {
 				lock (lockKnownViews) {
 
 
@@ -109,8 +109,8 @@ namespace UICatalog {
 						if(supported.Contains(kvp.Key))
 						if(supported.Contains(kvp.Key))
 						{
 						{
 							// if the key was bound to any other commands clear that
 							// 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
 						// 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)
 		private void RemapKey (object sender, EventArgs e)
 		{
 		{
 			var cmd = commands [commandsListView.SelectedItem];
 			var cmd = commands [commandsListView.SelectedItem];
-			Key? key = null;
+			KeyCode? key = null;
 
 
 			// prompt user to hit a key
 			// prompt user to hit a key
 			var dlg = new Dialog () { Title = "Enter 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.RequestStop ();
 			};
 			};
 			Application.Run (dlg);
 			Application.Run (dlg);

+ 4 - 0
UICatalog/Properties/launchSettings.json

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

+ 10 - 10
UICatalog/Scenarios/ASCIICustomButton.cs

@@ -21,7 +21,7 @@ namespace UICatalog.Scenarios {
 						CheckType = MenuItemCheckStyle.Checked
 						CheckType = MenuItemCheckStyle.Checked
 					},
 					},
 					null,
 					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);
 			Application.Top.Add (menu, _scrollViewTestWindow);
@@ -193,7 +193,7 @@ namespace UICatalog.Scenarios {
 					};
 					};
 				}
 				}
 
 
-				scrollView.ClearKeyBindings ();
+				scrollView.KeyBindings.Clear ();
 
 
 				buttons = new List<Button> ();
 				buttons = new List<Button> ();
 				Button prevButton = null;
 				Button prevButton = null;
@@ -205,7 +205,7 @@ namespace UICatalog.Scenarios {
 					button.Clicked += Button_Clicked;
 					button.Clicked += Button_Clicked;
 					button.PointerEnter += Button_PointerEnter;
 					button.PointerEnter += Button_PointerEnter;
 					button.MouseClick += Button_MouseClick;
 					button.MouseClick += Button_MouseClick;
-					button.KeyPressed += Button_KeyPress;
+					button.KeyDown += Button_KeyPress;
 					scrollView.Add (button);
 					scrollView.Add (button);
 					buttons.Add (button);
 					buttons.Add (button);
 					prevButton = button;
 					prevButton = button;
@@ -215,7 +215,7 @@ namespace UICatalog.Scenarios {
 				closeButton.Clicked += Button_Clicked;
 				closeButton.Clicked += Button_Clicked;
 				closeButton.PointerEnter += Button_PointerEnter;
 				closeButton.PointerEnter += Button_PointerEnter;
 				closeButton.MouseClick += Button_MouseClick;
 				closeButton.MouseClick += Button_MouseClick;
-				closeButton.KeyPressed += Button_KeyPress;
+				closeButton.KeyDown += Button_KeyPress;
 				scrollView.Add (closeButton);
 				scrollView.Add (closeButton);
 				buttons.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.ContentOffset = new Point (scrollView.ContentOffset.X,
 						 -(scrollView.ContentSize.Height - scrollView.Frame.Height
 						 -(scrollView.ContentSize.Height - scrollView.Frame.Height
 						 + (scrollView.ShowHorizontalScrollIndicator ? 1 : 0)));
 						 + (scrollView.ShowHorizontalScrollIndicator ? 1 : 0)));
 					obj.Handled = true;
 					obj.Handled = true;
 					return;
 					return;
-				case Key.Home:
+				case KeyCode.Home:
 					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X, 0);
 					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X, 0);
 					obj.Handled = true;
 					obj.Handled = true;
 					return;
 					return;
-				case Key.PageDown:
+				case KeyCode.PageDown:
 					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
 					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
 						 Math.Max (scrollView.ContentOffset.Y - scrollView.Frame.Height,
 						 Math.Max (scrollView.ContentOffset.Y - scrollView.Frame.Height,
 						 -(scrollView.ContentSize.Height - scrollView.Frame.Height
 						 -(scrollView.ContentSize.Height - scrollView.Frame.Height
 						 + (scrollView.ShowHorizontalScrollIndicator ? 1 : 0))));
 						 + (scrollView.ShowHorizontalScrollIndicator ? 1 : 0))));
 					obj.Handled = true;
 					obj.Handled = true;
 					return;
 					return;
-				case Key.PageUp:
+				case KeyCode.PageUp:
 					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
 					scrollView.ContentOffset = new Point (scrollView.ContentOffset.X,
 						 Math.Min (scrollView.ContentOffset.Y + scrollView.Frame.Height, 0));
 						 Math.Min (scrollView.ContentOffset.Y + scrollView.Frame.Height, 0));
 					obj.Handled = true;
 					obj.Handled = true;

+ 2 - 2
UICatalog/Scenarios/AllViewsTester.cs

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

+ 7 - 7
UICatalog/Scenarios/BackgroundWorkerCollection.cs

@@ -33,10 +33,10 @@ namespace UICatalog.Scenarios {
 
 
 				menu = new MenuBar (new MenuBarItem [] {
 				menu = new MenuBar (new MenuBarItem [] {
 					new MenuBarItem ("_Options", new MenuItem [] {
 					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,
 						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 ("_View", new MenuItem [] { }),
 					new MenuBarItem ("_Window", new MenuItem [] { })
 					new MenuBarItem ("_Window", new MenuItem [] { })
@@ -46,8 +46,8 @@ namespace UICatalog.Scenarios {
 
 
 				var statusBar = new StatusBar (new [] {
 				var statusBar = new StatusBar (new [] {
 					new StatusItem(Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit()),
 					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);
 				Add (statusBar);
 
 
@@ -338,8 +338,8 @@ namespace UICatalog.Scenarios {
 				close.Clicked += OnReportClosed;
 				close.Clicked += OnReportClosed;
 				Add (close);
 				Add (close);
 
 
-				KeyPressed += (s, e) => {
-					if (e.KeyEvent.Key == Key.Esc) {
+				KeyDown += (s, e) => {
+					if (e.KeyCode == KeyCode.Esc) {
 						OnReportClosed (this, EventArgs.Empty);
 						OnReportClosed (this, EventArgs.Empty);
 					}
 					}
 				};
 				};

+ 242 - 244
UICatalog/Scenarios/Buttons.cs

@@ -1,278 +1,276 @@
 using System.Text;
 using System.Text;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
 using Terminal.Gui;
 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));
 				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
 ///   - Helps test unicode character rendering in Terminal.Gui
 ///   - Illustrates how to use ScrollView to do infinite scrolling
 ///   - Illustrates how to use ScrollView to do infinite scrolling
 /// </summary>
 /// </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 ("Text and Formatting")]
 [ScenarioCategory ("Controls")]
 [ScenarioCategory ("Controls")]
 [ScenarioCategory ("ScrollView")]
 [ScenarioCategory ("ScrollView")]
@@ -48,9 +48,15 @@ public class CharacterMap : Scenario {
 		};
 		};
 		Application.Top.Add (_charMap);
 		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);
 		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);
 		Application.Top.Add (jumpEdit);
 		_errorLabel = new Label ("err") { X = Pos.Right (jumpEdit) + 1, Y = Pos.Y (_charMap), ColorScheme = Colors.ColorSchemes ["error"] };
 		_errorLabel = new Label ("err") { X = Pos.Right (jumpEdit) + 1, Y = Pos.Y (_charMap), ColorScheme = Colors.ColorSchemes ["error"] };
 		Application.Top.Add (_errorLabel);
 		Application.Top.Add (_errorLabel);
@@ -72,7 +78,7 @@ public class CharacterMap : Scenario {
 		//jumpList.Style.ShowVerticalHeaderLines = false;
 		//jumpList.Style.ShowVerticalHeaderLines = false;
 		_categoryList.Style.AlwaysShowHeaders = true;
 		_categoryList.Style.AlwaysShowHeaders = true;
 
 
-		var isDescending = false;
+		bool isDescending = false;
 
 
 		_categoryList.Table = CreateCategoryTable (0, isDescending);
 		_categoryList.Table = CreateCategoryTable (0, isDescending);
 
 
@@ -81,19 +87,19 @@ public class CharacterMap : Scenario {
 			_categoryList.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out int? clickedCol);
 			_categoryList.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out int? clickedCol);
 			if (clickedCol != null && e.MouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)) {
 			if (clickedCol != null && e.MouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)) {
 				var table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
 				var table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
-				var prevSelection = table.Data.ElementAt (_categoryList.SelectedRow).Category;
+				string prevSelection = table.Data.ElementAt (_categoryList.SelectedRow).Category;
 				isDescending = !isDescending;
 				isDescending = !isDescending;
 
 
 				_categoryList.Table = CreateCategoryTable (clickedCol.Value, isDescending);
 				_categoryList.Table = CreateCategoryTable (clickedCol.Value, isDescending);
 
 
 				table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
 				table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
 				_categoryList.SelectedRow = table.Data
 				_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 (0, new ColumnStyle () { MaxWidth = longestName, MinWidth = longestName, MinAcceptableWidth = longestName });
 		_categoryList.Style.ColumnStyles.Add (1, new ColumnStyle () { MaxWidth = 1, MinWidth = 6 });
 		_categoryList.Style.ColumnStyles.Add (1, new ColumnStyle () { MaxWidth = 1, MinWidth = 6 });
 		_categoryList.Style.ColumnStyles.Add (2, 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.Width = _categoryList.Style.ColumnStyles.Sum (c => c.Value.MinWidth) + 4;
 
 
 		_categoryList.SelectedCellChanged += (s, args) => {
 		_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;
 			_charMap.StartCodePoint = table.Data.ToArray () [args.NewRow].Start;
 		};
 		};
 
 
@@ -114,11 +120,11 @@ public class CharacterMap : Scenario {
 		_charMap.Width = Dim.Fill () - _categoryList.Width;
 		_charMap.Width = Dim.Fill () - _categoryList.Width;
 
 
 		var menu = new MenuBar (new MenuBarItem [] {
 		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);
 		Application.Top.Add (menu);
@@ -131,7 +137,7 @@ public class CharacterMap : Scenario {
 	MenuItem CreateMenuShowWidth ()
 	MenuItem CreateMenuShowWidth ()
 	{
 	{
 		var item = new MenuItem {
 		var item = new MenuItem {
-			Title = "_Show Glyph Width",
+			Title = "_Show Glyph Width"
 		};
 		};
 		item.CheckType |= MenuItemCheckStyle.Checked;
 		item.CheckType |= MenuItemCheckStyle.Checked;
 		item.Checked = _charMap?.ShowGlyphWidths;
 		item.Checked = _charMap?.ShowGlyphWidths;
@@ -145,11 +151,11 @@ public class CharacterMap : Scenario {
 	EnumerableTableSource<UnicodeRange> CreateCategoryTable (int sortByColumn, bool descending)
 	EnumerableTableSource<UnicodeRange> CreateCategoryTable (int sortByColumn, bool descending)
 	{
 	{
 		Func<UnicodeRange, object> orderBy;
 		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) {
 		switch (sortByColumn) {
 		case 0:
 		case 0:
 			orderBy = r => r.Category;
 			orderBy = r => r.Category;
@@ -167,19 +173,18 @@ public class CharacterMap : Scenario {
 			throw new ArgumentException ("Invalid column number.");
 			throw new ArgumentException ("Invalid column number.");
 		}
 		}
 
 
-		IOrderedEnumerable<UnicodeRange> sortedRanges = descending ?
+		var sortedRanges = descending ?
 			UnicodeRange.Ranges.OrderByDescending (orderBy) :
 			UnicodeRange.Ranges.OrderByDescending (orderBy) :
 			UnicodeRange.Ranges.OrderBy (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 },
 			{ $"Category{categorySort}", s => s.Category },
 			{ $"Start{startSort}", s => $"{s.Start:x5}" },
 			{ $"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;
 		var jumpEdit = sender as TextField;
 		if (jumpEdit.Text.Length == 0) {
 		if (jumpEdit.Text.Length == 0) {
@@ -217,8 +222,8 @@ public class CharacterMap : Scenario {
 
 
 		var table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
 		var table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
 		_categoryList.SelectedRow = table.Data
 		_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 ();
 		_categoryList.EnsureSelectedCellIsVisible ();
 
 
 		// Ensure the typed glyph is selected 
 		// Ensure the typed glyph is selected 
@@ -227,7 +232,6 @@ public class CharacterMap : Scenario {
 }
 }
 
 
 class CharMap : ScrollView {
 class CharMap : ScrollView {
-
 	/// <summary>
 	/// <summary>
 	/// Specifies the starting offset for the character map. The default is 0x2500 
 	/// Specifies the starting offset for the character map. The default is 0x2500 
 	/// which is the Box Drawing characters.
 	/// which is the Box Drawing characters.
@@ -251,10 +255,10 @@ class CharMap : ScrollView {
 		get => _selected;
 		get => _selected;
 		set {
 		set {
 			_selected = value;
 			_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) {
 			if (row + ContentOffset.Y < 0) {
 				// Moving up.
 				// Moving up.
 				ContentOffset = new Point (ContentOffset.X, row);
 				ContentOffset = new Point (ContentOffset.X, row);
@@ -262,7 +266,7 @@ class CharMap : ScrollView {
 				// Moving down.
 				// Moving down.
 				ContentOffset = new Point (ContentOffset.X, Math.Min (row, row - height + _rowHeight));
 				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) {
 			if (col + ContentOffset.X < 0) {
 				// Moving left.
 				// Moving left.
 				ContentOffset = new Point (col, ContentOffset.Y);
 				ContentOffset = new Point (col, ContentOffset.Y);
@@ -282,8 +286,8 @@ class CharMap : ScrollView {
 	/// </summary>
 	/// </summary>
 	public Point Cursor {
 	public Point Cursor {
 		get {
 		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);
 			return new Point (col, row);
 		}
 		}
 		set => throw new NotImplementedException ();
 		set => throw new NotImplementedException ();
@@ -292,10 +296,10 @@ class CharMap : ScrollView {
 	public override void PositionCursor ()
 	public override void PositionCursor ()
 	{
 	{
 		if (HasFocus &&
 		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);
 			Driver.SetCursorVisibility (CursorVisibility.Default);
 			Move (Cursor.X, Cursor.Y);
 			Move (Cursor.X, Cursor.Y);
 		} else {
 		} else {
@@ -320,13 +324,14 @@ class CharMap : ScrollView {
 	public static int MaxCodePoint => 0x10FFFF;
 	public static int MaxCodePoint => 0x10FFFF;
 
 
 	static int RowLabelWidth => $"U+{MaxCodePoint:x5}".Length + 1;
 	static int RowLabelWidth => $"U+{MaxCodePoint:x5}".Length + 1;
-	static int RowWidth => RowLabelWidth + (COLUMN_WIDTH * 16);
+
+	static int RowWidth => RowLabelWidth + COLUMN_WIDTH * 16;
 
 
 	public CharMap ()
 	public CharMap ()
 	{
 	{
 		ColorScheme = Colors.Dialog;
 		ColorScheme = Colors.Dialog;
 		CanFocus = true;
 		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, () => {
 		AddCommand (Command.ScrollUp, () => {
 			if (SelectedCodePoint >= 16) {
 			if (SelectedCodePoint >= 16) {
@@ -353,12 +358,12 @@ class CharMap : ScrollView {
 			return true;
 			return true;
 		});
 		});
 		AddCommand (Command.PageUp, () => {
 		AddCommand (Command.PageUp, () => {
-			var page = (Bounds.Height / _rowHeight - 1) * 16;
+			int page = (Bounds.Height / _rowHeight - 1) * 16;
 			SelectedCodePoint -= Math.Min (page, SelectedCodePoint);
 			SelectedCodePoint -= Math.Min (page, SelectedCodePoint);
 			return true;
 			return true;
 		});
 		});
 		AddCommand (Command.PageDown, () => {
 		AddCommand (Command.PageDown, () => {
-			var page = (Bounds.Height / _rowHeight - 1) * 16;
+			int page = (Bounds.Height / _rowHeight - 1) * 16;
 			SelectedCodePoint += Math.Min (page, MaxCodePoint - SelectedCodePoint);
 			SelectedCodePoint += Math.Min (page, MaxCodePoint - SelectedCodePoint);
 			return true;
 			return true;
 		});
 		});
@@ -370,7 +375,7 @@ class CharMap : ScrollView {
 			SelectedCodePoint = MaxCodePoint;
 			SelectedCodePoint = MaxCodePoint;
 			return true;
 			return true;
 		});
 		});
-		AddKeyBinding (Key.Enter, Command.Accept);
+		KeyBindings.Add (Key.Enter, Command.Accept);
 		AddCommand (Command.Accept, () => {
 		AddCommand (Command.Accept, () => {
 			ShowDetails ();
 			ShowDetails ();
 			return true;
 			return true;
@@ -379,11 +384,10 @@ class CharMap : ScrollView {
 		MouseClick += Handle_MouseClick;
 		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)) {
 		//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 + 2));
 		//	//ContentSize = new Size (CharMap.RowWidth, (int)(MaxCodePoint / 16) * _rowHeight + 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);
 		//	ContentOffset = new Point (0, ContentOffset.Y < -ContentSize.Height + Bounds.Height ? ContentOffset.Y - 1 : ContentOffset.Y);
 		//}
 		//}
 		base.OnDrawContent (contentArea);
 		base.OnDrawContent (contentArea);
-	}
 
 
 	//public void CharMap_DrawContent (object s, DrawEventArgs a)
 	//public void CharMap_DrawContent (object s, DrawEventArgs a)
 	public override void OnDrawContentComplete (Rect contentArea)
 	public override void OnDrawContentComplete (Rect contentArea)
@@ -412,7 +415,7 @@ class CharMap : ScrollView {
 		if (contentArea.Height == 0 || contentArea.Width == 0) {
 		if (contentArea.Height == 0 || contentArea.Width == 0) {
 			return;
 			return;
 		}
 		}
-		Rect viewport = new Rect (ContentOffset,
+		var viewport = new Rect (ContentOffset,
 			new Size (Math.Max (Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0), 0),
 			new Size (Math.Max (Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0), 0),
 				Math.Max (Bounds.Height - (ShowHorizontalScrollIndicator ? 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));
 			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 ());
 		Driver.SetAttribute (GetHotNormalColor ());
 		Move (0, 0);
 		Move (0, 0);
 		Driver.AddStr (new string (' ', RowLabelWidth + 1));
 		Driver.AddStr (new string (' ', RowLabelWidth + 1));
 		for (int hexDigit = 0; hexDigit < 16; hexDigit++) {
 		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) {
 			if (x > RowLabelWidth - 2) {
 				Move (x, 0);
 				Move (x, 0);
 				Driver.SetAttribute (GetHotNormalColor ());
 				Driver.SetAttribute (GetHotNormalColor ());
 				Driver.AddStr (" ");
 				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.AddStr ($"{hexDigit:x}");
 				Driver.SetAttribute (GetHotNormalColor ());
 				Driver.SetAttribute (GetHotNormalColor ());
 				Driver.AddStr (" ");
 				Driver.AddStr (" ");
 			}
 			}
 		}
 		}
 
 
-		var firstColumnX = viewport.X + RowLabelWidth;
+		int firstColumnX = viewport.X + RowLabelWidth;
 		for (int y = 1; y < Bounds.Height; y++) {
 		for (int y = 1; y < Bounds.Height; y++) {
 			// What row is this?
 			// 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) {
 			if (val > MaxCodePoint) {
 				continue;
 				continue;
 			}
 			}
@@ -458,18 +461,18 @@ class CharMap : ScrollView {
 			Driver.SetAttribute (GetNormalColor ());
 			Driver.SetAttribute (GetNormalColor ());
 			for (int col = 0; col < 16; col++) {
 			for (int col = 0; col < 16; col++) {
 
 
-				var x = firstColumnX + COLUMN_WIDTH * col + 1;
+				int x = firstColumnX + COLUMN_WIDTH * col + 1;
 
 
 				Move (x, y);
 				Move (x, y);
 				if (cursorRow + ContentOffset.Y + 1 == y && cursorCol + ContentOffset.X + firstColumnX + 1 == x && !HasFocus) {
 				if (cursorRow + ContentOffset.Y + 1 == y && cursorCol + ContentOffset.X + firstColumnX + 1 == x && !HasFocus) {
 					Driver.SetAttribute (GetFocusColor ());
 					Driver.SetAttribute (GetFocusColor ());
 				}
 				}
-				var scalar = val + col;
-				Rune rune = (Rune)'?';
+				int scalar = val + col;
+				var rune = (Rune)'?';
 				if (Rune.IsValid (scalar)) {
 				if (Rune.IsValid (scalar)) {
 					rune = new Rune (scalar);
 					rune = new Rune (scalar);
 				}
 				}
-				var width = rune.GetColumns ();
+				int width = rune.GetColumns ();
 
 
 				// are we at first row of the row?
 				// are we at first row of the row?
 				if (!ShowGlyphWidths || (y - ContentOffset.Y) % _rowHeight > 0) {
 				if (!ShowGlyphWidths || (y - ContentOffset.Y) % _rowHeight > 0) {
@@ -487,7 +490,7 @@ class CharMap : ScrollView {
 							sb.Append (rune);
 							sb.Append (rune);
 							// Try normalizing after combining with 'a'. If it normalizes, at least 
 							// Try normalizing after combining with 'a'. If it normalizes, at least 
 							// it'll show on the 'a'. If not, just show the replacement char.
 							// 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) {
 							if (normal.Length == 1) {
 								Driver.AddRune (normal [0]);
 								Driver.AddRune (normal [0]);
 							} else {
 							} else {
@@ -505,7 +508,7 @@ class CharMap : ScrollView {
 				}
 				}
 			}
 			}
 			Move (0, y);
 			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) {
 			if (!ShowGlyphWidths || (y - ContentOffset.Y) % _rowHeight > 0) {
 				Driver.AddStr ($"U+{val / 16:x5}_ ");
 				Driver.AddStr ($"U+{val / 16:x5}_ ");
 			} else {
 			} else {
@@ -515,12 +518,13 @@ class CharMap : ScrollView {
 		Driver.Clip = oldClip;
 		Driver.Clip = oldClip;
 	}
 	}
 
 
-	ContextMenu _contextMenu = new ContextMenu ();
+	ContextMenu _contextMenu = new ();
+
 	void Handle_MouseClick (object sender, MouseEventEventArgs args)
 	void Handle_MouseClick (object sender, MouseEventEventArgs args)
 	{
 	{
 		var me = args.MouseEvent;
 		var me = args.MouseEvent;
 		if (me.Flags != MouseFlags.ReportMousePosition && me.Flags != MouseFlags.Button1Clicked &&
 		if (me.Flags != MouseFlags.ReportMousePosition && me.Flags != MouseFlags.Button1Clicked &&
-			me.Flags != MouseFlags.Button1DoubleClicked) {
+		me.Flags != MouseFlags.Button1DoubleClicked) {
 			return;
 			return;
 		}
 		}
 
 
@@ -528,21 +532,20 @@ class CharMap : ScrollView {
 			me.Y = Cursor.Y;
 			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;
 			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) {
 		if (col > 15) {
 			col = 15;
 			col = 15;
 		}
 		}
 
 
-		var val = (row) * 16 + col;
+		int val = row * 16 + col;
 		if (val > MaxCodePoint) {
 		if (val > MaxCodePoint) {
 			return;
 			return;
 		}
 		}
@@ -566,11 +569,9 @@ class CharMap : ScrollView {
 			SelectedCodePoint = val;
 			SelectedCodePoint = val;
 			_contextMenu = new ContextMenu (me.X + 1, me.Y + 1,
 			_contextMenu = new ContextMenu (me.X + 1, me.Y + 1,
 				new MenuBarItem (new MenuItem [] {
 				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 ();
 			_contextMenu.Show ();
 		}
 		}
@@ -582,7 +583,7 @@ class CharMap : ScrollView {
 			return str;
 			return str;
 		}
 		}
 
 
-		TextInfo textInfo = new CultureInfo ("en-US", false).TextInfo;
+		var textInfo = new CultureInfo ("en-US", false).TextInfo;
 
 
 		str = textInfo.ToLower (str);
 		str = textInfo.ToLower (str);
 		str = textInfo.ToTitleCase (str);
 		str = textInfo.ToTitleCase (str);
@@ -614,7 +615,7 @@ class CharMap : ScrollView {
 		var spinner = new SpinnerView () {
 		var spinner = new SpinnerView () {
 			X = Pos.Center (),
 			X = Pos.Center (),
 			Y = Pos.Center (),
 			Y = Pos.Center (),
-			Style = new SpinnerStyle.Aesthetic (),
+			Style = new Aesthetic ()
 
 
 		};
 		};
 		spinner.AutoSpin = true;
 		spinner.AutoSpin = true;
@@ -640,11 +641,11 @@ class CharMap : ScrollView {
 		if (!string.IsNullOrEmpty (decResponse)) {
 		if (!string.IsNullOrEmpty (decResponse)) {
 			string name = string.Empty;
 			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
 				// 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 ();
 					name = nameElement.GetString ();
 				}
 				}
 
 
@@ -654,12 +655,12 @@ class CharMap : ScrollView {
 				//	Console.WriteLine (nestedPropertyElement.GetString ());
 				//	Console.WriteLine (nestedPropertyElement.GetString ());
 				//}
 				//}
 				decResponse = JsonSerializer.Serialize (document.RootElement, new
 				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 copyGlyph = new Button ("Copy _Glyph");
 			var copyCP = new Button ("Copy Code _Point");
 			var copyCP = new Button ("Copy Code _Point");
@@ -819,7 +820,7 @@ class CharMap : ScrollView {
 }
 }
 
 
 public class UcdApiClient {
 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 const string BaseUrl = "https://ucdapi.org/unicode/latest/";
 
 
 	public async Task<string> GetCodepointHex (string hex)
 	public async Task<string> GetCodepointHex (string hex)
@@ -851,49 +852,49 @@ public class UcdApiClient {
 	}
 	}
 }
 }
 
 
-
 class UnicodeRange {
 class UnicodeRange {
 	public int Start;
 	public int Start;
 	public int End;
 	public int End;
 	public string Category;
 	public string Category;
+
 	public UnicodeRange (int start, int end, 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 ()
 	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
 		// .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> {
 		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 ();
 		return ranges.Concat (nonBmpRanges).OrderBy (r => r.Category).ToList ();

+ 1 - 1
UICatalog/Scenarios/CollectionNavigatorTester.cs

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

+ 3 - 3
UICatalog/Scenarios/ConfigurationEditor.cs

@@ -49,11 +49,11 @@ namespace UICatalog.Scenarios {
 
 
 			Application.Top.Add (_tileView);
 			Application.Top.Add (_tileView);
 
 
-			_lenStatusItem = new StatusItem (Key.CharMask, "Len: ", null);
+			_lenStatusItem = new StatusItem (KeyCode.CharMask, "Len: ", null);
 			var statusBar = new StatusBar (new StatusItem [] {
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Application.QuitKey, $"{Application.QuitKey} Quit", () => Quit()),
 				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,
 				_lenStatusItem,
 			});
 			});
 
 

+ 132 - 126
UICatalog/Scenarios/ContextMenus.cs

@@ -3,97 +3,104 @@ using System.Globalization;
 using System.Threading;
 using System.Threading;
 using Terminal.Gui;
 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 MenuItem ("_Configuration", "Show configuration", () => MessageBox.Query (50, 5, "Info", "This would open settings dialog", "Ok")),
 					new MenuBarItem ("More options", new MenuItem [] {
 					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 MenuItem ("_Maintenance", "Maintenance mode", () => MessageBox.Query (50, 5, "Info", "This would open maintenance dialog", "Ok")),
 					}),
 					}),
 					new MenuBarItem ("_Languages", GetSupportedCultures ()),
 					new MenuBarItem ("_Languages", GetSupportedCultures ()),
@@ -111,56 +118,55 @@ namespace UICatalog.Scenarios {
 						},
 						},
 					null,
 					null,
 					new MenuItem ("_Quit", "", () => Application.RequestStop ())
 					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);
 				CreateAction (supportedCultures, culture);
 				supportedCultures.Add (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);
 			Application.Top.Add (menu);
 
 
 			var statusBar = new StatusBar (new StatusItem [] {
 			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()),
 				new StatusItem(Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit()),
 			});
 			});
 			Application.Top.Add (statusBar);
 			Application.Top.Add (statusBar);
@@ -88,7 +88,7 @@ namespace UICatalog.Scenarios {
 
 
 			tableView.SelectedCellChanged += OnSelectedCellChanged;
 			tableView.SelectedCellChanged += OnSelectedCellChanged;
 			tableView.CellActivated += EditCurrentCell;
 			tableView.CellActivated += EditCurrentCell;
-			tableView.KeyPressed += TableViewKeyPress;
+			tableView.KeyDown += TableViewKeyPress;
 
 
 			SetupScrollBar ();
 			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) {
 				if (tableView.FullRowSelect) {
 					// Delete button deletes all rows when in full row mode
 					// 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
 					Height = 4
 				};
 				};
 
 
-				var _txtDelimiter = new TextField (MenuBar.ShortcutDelimiter) {
+				var _txtDelimiter = new TextField (MenuBar.ShortcutDelimiter.ToString()) {
 					X = Pos.Center (),
 					X = Pos.Center (),
 					Width = 2,
 					Width = 2,
 				};
 				};
-				_txtDelimiter.TextChanged += (s, _) => MenuBar.ShortcutDelimiter = _txtDelimiter.Text;
+				_txtDelimiter.TextChanged += (s, _) => MenuBar.ShortcutDelimiter = _txtDelimiter.Text.ToRunes()[0];
 				_frmDelimiter.Add (_txtDelimiter);
 				_frmDelimiter.Add (_txtDelimiter);
 
 
 				Add (_frmDelimiter);
 				Add (_frmDelimiter);
@@ -723,30 +723,28 @@ namespace UICatalog.Scenarios {
 					ReadOnly = true
 					ReadOnly = true
 				};
 				};
 				_txtShortcut.KeyDown += (s, e) => {
 				_txtShortcut.KeyDown += (s, e) => {
-					if (!ProcessKey (e.KeyEvent)) {
+					if (!ProcessKey (e)) {
 						return;
 						return;
 					}
 					}
-
-					var k = ShortcutHelper.GetModifiersKey (e.KeyEvent);
-					if (CheckShortcut (k, true)) {
+					if (CheckShortcut (e.KeyCode, true)) {
 						e.Handled = 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 false;
 					}
 					}
 
 
 					return true;
 					return true;
 				}
 				}
 
 
-				bool CheckShortcut (Key k, bool pre)
+				bool CheckShortcut (KeyCode k, bool pre)
 				{
 				{
 					var m = _menuItem != null ? _menuItem : new MenuItem ();
 					var m = _menuItem != null ? _menuItem : new MenuItem ();
 					if (pre && !ShortcutHelper.PreShortcutValidation (k)) {
 					if (pre && !ShortcutHelper.PreShortcutValidation (k)) {
@@ -760,14 +758,13 @@ namespace UICatalog.Scenarios {
 						}
 						}
 						return true;
 						return true;
 					}
 					}
-					_txtShortcut.Text = ShortcutHelper.GetShortcutTag (k);
+					_txtShortcut.Text = Key.ToString (k, MenuBar.ShortcutDelimiter);// ShortcutHelper.GetShortcutTag (k);
 
 
 					return true;
 					return true;
 				}
 				}
 
 
 				_txtShortcut.KeyUp += (s, e) => {
 				_txtShortcut.KeyUp += (s, e) => {
-					var k = ShortcutHelper.GetModifiersKey (e.KeyEvent);
-					if (CheckShortcut (k, false)) {
+					if (CheckShortcut (e.KeyCode, false)) {
 						e.Handled = true;
 						e.Handled = true;
 					}
 					}
 				};
 				};

+ 540 - 543
UICatalog/Scenarios/DynamicStatusBar.cs

@@ -7,652 +7,649 @@ using System.Reflection;
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
 using Terminal.Gui;
 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 ();
 					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 = "";
 						_txtShortcut.Text = "";
 						return false;
 						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;
 					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 {
 				} 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);
 		}
 		}
 	}
 	}
 }
 }

Some files were not shown because too many files changed in this diff