Browse Source

Fixed #3786 - Focus

Tig 9 months ago
parent
commit
573d6ee0ff

+ 1 - 0
Terminal.Gui/Application/ApplicationNavigation.cs

@@ -77,6 +77,7 @@ public class ApplicationNavigation
         {
         {
             return;
             return;
         }
         }
+        Debug.Assert (value is null or { CanFocus: true, HasFocus: true });
 
 
         _focused = value;
         _focused = value;
 
 

+ 7 - 0
Terminal.Gui/View/View.Hierarchy.cs

@@ -169,6 +169,13 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
         Debug.Assert (!view.HasFocus);
         Debug.Assert (!view.HasFocus);
 
 
         _subviews.Remove (view);
         _subviews.Remove (view);
+
+        // Clean up focus stuff
+        _previouslyFocused = null;
+        if (view._superView is { } && view._superView._previouslyFocused == this)
+        {
+            view._superView._previouslyFocused = null;
+        }
         view._superView = null;
         view._superView = null;
 
 
         SetNeedsLayout ();
         SetNeedsLayout ();

+ 68 - 49
Terminal.Gui/View/View.Navigation.cs

@@ -180,7 +180,8 @@ public partial class View // Focus and cross-view navigation management (TabStop
             if (!_canFocus && HasFocus)
             if (!_canFocus && HasFocus)
             {
             {
                 // If CanFocus is set to false and this view has focus, make it leave focus
                 // If CanFocus is set to false and this view has focus, make it leave focus
-                HasFocus = false;
+                // Set traverssingdown so we don't go back up the hierachy...
+                SetHasFocusFalse (null, traversingDown: false);
             }
             }
 
 
             if (_canFocus && !HasFocus && Visible && SuperView is { Focused: null })
             if (_canFocus && !HasFocus && Visible && SuperView is { Focused: null })
@@ -296,7 +297,12 @@ public partial class View // Focus and cross-view navigation management (TabStop
 
 
         if (Focused is null && _previouslyFocused is { } && indicies.Contains (_previouslyFocused))
         if (Focused is null && _previouslyFocused is { } && indicies.Contains (_previouslyFocused))
         {
         {
-            return _previouslyFocused.SetFocus ();
+            if (_previouslyFocused.SetFocus ())
+            {
+                return true;
+            }
+
+            _previouslyFocused = null;
         }
         }
 
 
         return false;
         return false;
@@ -412,14 +418,14 @@ public partial class View // Focus and cross-view navigation management (TabStop
     ///     other methods that
     ///     other methods that
     ///     set or remove focus from a view.
     ///     set or remove focus from a view.
     /// </summary>
     /// </summary>
-    /// <param name="previousFocusedView">
-    ///     The previously focused view. If <see langword="null"/> there is no previously focused
+    /// <param name="currentFocusedView">
+    ///     The currently focused view. If <see langword="null"/> there is no previously focused
     ///     view.
     ///     view.
     /// </param>
     /// </param>
     /// <param name="traversingUp"></param>
     /// <param name="traversingUp"></param>
     /// <returns><see langword="true"/> if <see cref="HasFocus"/> was changed to <see langword="true"/>.</returns>
     /// <returns><see langword="true"/> if <see cref="HasFocus"/> was changed to <see langword="true"/>.</returns>
     /// <exception cref="InvalidOperationException"></exception>
     /// <exception cref="InvalidOperationException"></exception>
-    private (bool focusSet, bool cancelled) SetHasFocusTrue (View? previousFocusedView, bool traversingUp = false)
+    private (bool focusSet, bool cancelled) SetHasFocusTrue (View? currentFocusedView, bool traversingUp = false)
     {
     {
         Debug.Assert (SuperView is null || ApplicationNavigation.IsInHierarchy (SuperView, this));
         Debug.Assert (SuperView is null || ApplicationNavigation.IsInHierarchy (SuperView, this));
 
 
@@ -429,6 +435,11 @@ public partial class View // Focus and cross-view navigation management (TabStop
             return (false, false);
             return (false, false);
         }
         }
 
 
+        if (currentFocusedView is { HasFocus: false })
+        {
+            throw new ArgumentException ("SetHasFocusTrue: currentFocusedView must HasFocus.");
+        }
+
         var thisAsAdornment = this as Adornment;
         var thisAsAdornment = this as Adornment;
         View? superViewOrParent = thisAsAdornment?.Parent ?? SuperView;
         View? superViewOrParent = thisAsAdornment?.Parent ?? SuperView;
 
 
@@ -451,7 +462,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
 
 
         bool previousValue = HasFocus;
         bool previousValue = HasFocus;
 
 
-        bool cancelled = NotifyFocusChanging (false, true, previousFocusedView, this);
+        bool cancelled = NotifyFocusChanging (false, true, currentFocusedView, this);
 
 
         if (cancelled)
         if (cancelled)
         {
         {
@@ -462,7 +473,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
         // Any of them may cancel gaining focus. In which case we need to back out.
         // Any of them may cancel gaining focus. In which case we need to back out.
         if (superViewOrParent is { HasFocus: false } sv)
         if (superViewOrParent is { HasFocus: false } sv)
         {
         {
-            (bool focusSet, bool svCancelled) = sv.SetHasFocusTrue (previousFocusedView, true);
+            (bool focusSet, bool svCancelled) = sv.SetHasFocusTrue (currentFocusedView, true);
 
 
             if (!focusSet)
             if (!focusSet)
             {
             {
@@ -488,31 +499,33 @@ public partial class View // Focus and cross-view navigation management (TabStop
 
 
         if (!traversingUp)
         if (!traversingUp)
         {
         {
-            // Restore focus to the previously focused subview 
+            // Restore focus to the previously focused subview, if any
             if (!RestoreFocus ())
             if (!RestoreFocus ())
             {
             {
-                // Couldn't restore focus, so use Advance to navigate to the next focusable subview
-                if (!AdvanceFocus (NavigationDirection.Forward, null))
-                {
-                    // Couldn't advance, so we're the most focused view in the application
-                    Application.Navigation?.SetFocused (this);
-                }
+                Debug.Assert (_previouslyFocused is null);
+                // Couldn't restore focus, so use Advance to navigate to the next focusable subview, if any
+                AdvanceFocus (NavigationDirection.Forward, null);
             }
             }
         }
         }
 
 
-        if (previousFocusedView is { HasFocus: true } && GetFocusChain (NavigationDirection.Forward, TabStop).Contains (previousFocusedView))
+        // Now make sure the old focused view loses focus
+        if (currentFocusedView is { HasFocus: true } && GetFocusChain (NavigationDirection.Forward, TabStop).Contains (currentFocusedView))
         {
         {
-            previousFocusedView.SetHasFocusFalse (this);
+            currentFocusedView.SetHasFocusFalse (this);
         }
         }
 
 
-        _previouslyFocused = null;
+        if (_previouslyFocused is { })
+        {
+            _previouslyFocused = null;
+        }
 
 
         if (Arrangement.HasFlag (ViewArrangement.Overlapped))
         if (Arrangement.HasFlag (ViewArrangement.Overlapped))
         {
         {
             SuperView?.MoveSubviewToEnd (this);
             SuperView?.MoveSubviewToEnd (this);
         }
         }
 
 
-        NotifyFocusChanged (HasFocus, previousFocusedView, this);
+        // Focus work is done. Notify.
+        NotifyFocusChanged (HasFocus, currentFocusedView, this);
 
 
         SetNeedsDisplay ();
         SetNeedsDisplay ();
 
 
@@ -527,6 +540,9 @@ public partial class View // Focus and cross-view navigation management (TabStop
 
 
     private bool NotifyFocusChanging (bool currentHasFocus, bool newHasFocus, View? currentFocused, View? newFocused)
     private bool NotifyFocusChanging (bool currentHasFocus, bool newHasFocus, View? currentFocused, View? newFocused)
     {
     {
+        Debug.Assert (currentFocused is null || currentFocused is { HasFocus: true });
+        Debug.Assert (newFocused is null || newFocused is { CanFocus: true });
+
         // Call the virtual method
         // Call the virtual method
         if (OnHasFocusChanging (currentHasFocus, newHasFocus, currentFocused, newFocused))
         if (OnHasFocusChanging (currentHasFocus, newHasFocus, currentFocused, newFocused))
         {
         {
@@ -543,6 +559,13 @@ public partial class View // Focus and cross-view navigation management (TabStop
             return true;
             return true;
         }
         }
 
 
+        View? appFocused = Application.Navigation?.GetFocused ();
+
+        if (appFocused == currentFocused)
+        {
+            Application.Navigation?.SetFocused (null);
+        }
+
         return false;
         return false;
     }
     }
 
 
@@ -586,8 +609,8 @@ public partial class View // Focus and cross-view navigation management (TabStop
     ///     focused.
     ///     focused.
     /// </param>
     /// </param>
     /// <param name="traversingDown">
     /// <param name="traversingDown">
-    ///     Set to true to indicate method is being called recurively, traversing down the focus
-    ///     chain.
+    ///     Set to true to traverse down the focus
+    ///     chain only. If false, the method will attempt to AdvanceFocus on the superview or restorefocus on Application.Navigation.GetFocused().
     /// </param>
     /// </param>
     /// <exception cref="InvalidOperationException"></exception>
     /// <exception cref="InvalidOperationException"></exception>
     private void SetHasFocusFalse (View? newFocusedView, bool traversingDown = false)
     private void SetHasFocusFalse (View? newFocusedView, bool traversingDown = false)
@@ -598,53 +621,60 @@ public partial class View // Focus and cross-view navigation management (TabStop
             throw new InvalidOperationException ("SetHasFocusFalse should not be called if the view does not have focus.");
             throw new InvalidOperationException ("SetHasFocusFalse should not be called if the view does not have focus.");
         }
         }
 
 
+        if (newFocusedView is { HasFocus: false })
+        {
+            throw new InvalidOperationException ("SetHasFocusFalse new focused view does not have focus.");
+        }
+
         var thisAsAdornment = this as Adornment;
         var thisAsAdornment = this as Adornment;
         View? superViewOrParent = thisAsAdornment?.Parent ?? SuperView;
         View? superViewOrParent = thisAsAdornment?.Parent ?? SuperView;
 
 
         // If newFocusedVew is null, we need to find the view that should get focus, and SetFocus on it.
         // If newFocusedVew is null, we need to find the view that should get focus, and SetFocus on it.
         if (!traversingDown && newFocusedView is null)
         if (!traversingDown && newFocusedView is null)
         {
         {
-            if (superViewOrParent?._previouslyFocused is { })
+            if (superViewOrParent?._previouslyFocused is { CanFocus: true })
             {
             {
-                if (superViewOrParent._previouslyFocused != this)
+                if (superViewOrParent._previouslyFocused != this && superViewOrParent._previouslyFocused.SetFocus ())
                 {
                 {
-                    superViewOrParent?._previouslyFocused?.SetFocus ();
-
                     // The above will cause SetHasFocusFalse, so we can return
                     // The above will cause SetHasFocusFalse, so we can return
+                    Debug.Assert (!_hasFocus);
                     return;
                     return;
                 }
                 }
             }
             }
 
 
-            if (superViewOrParent is { })
+            if (superViewOrParent is { CanFocus: true })
             {
             {
                 if (superViewOrParent.AdvanceFocus (NavigationDirection.Forward, TabStop))
                 if (superViewOrParent.AdvanceFocus (NavigationDirection.Forward, TabStop))
                 {
                 {
                     // The above will cause SetHasFocusFalse, so we can return
                     // The above will cause SetHasFocusFalse, so we can return
+                    Debug.Assert (!_hasFocus);
                     return;
                     return;
                 }
                 }
 
 
-                newFocusedView = superViewOrParent;
+                if (superViewOrParent is { HasFocus: true, CanFocus: true })
+                {
+                    newFocusedView = superViewOrParent;
+                }
             }
             }
 
 
-            if (Application.Navigation is { } && Application.Top is { })
+            if (Application.Navigation is { } && Application.Navigation.GetFocused () is { CanFocus: true })
             {
             {
                 // Temporarily ensure this view can't get focus
                 // Temporarily ensure this view can't get focus
                 bool prevCanFocus = _canFocus;
                 bool prevCanFocus = _canFocus;
                 _canFocus = false;
                 _canFocus = false;
-                bool restoredFocus = Application.Top!.RestoreFocus ();
+                bool restoredFocus = Application.Navigation.GetFocused ()!.RestoreFocus ();
                 _canFocus = prevCanFocus;
                 _canFocus = prevCanFocus;
 
 
                 if (restoredFocus)
                 if (restoredFocus)
                 {
                 {
                     // The above caused SetHasFocusFalse, so we can return
                     // The above caused SetHasFocusFalse, so we can return
+                    Debug.Assert (!_hasFocus);
                     return;
                     return;
                 }
                 }
             }
             }
-
             // No other focusable view to be found. Just "leave" us...
             // No other focusable view to be found. Just "leave" us...
         }
         }
 
 
-
         // Before we can leave focus, we need to make sure that all views down the subview-hierarchy have left focus.
         // Before we can leave focus, we need to make sure that all views down the subview-hierarchy have left focus.
         View? mostFocused = MostFocused;
         View? mostFocused = MostFocused;
 
 
@@ -665,15 +695,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
                 bottom = bottom.SuperView;
                 bottom = bottom.SuperView;
             }
             }
 
 
-            if (bottom == this && bottom.SuperView is Adornment a)
-            {
-                //a.SetHasFocusFalse (newFocusedView, true);
-
-                Debug.Assert (_hasFocus);
-            }
-
             Debug.Assert (_hasFocus);
             Debug.Assert (_hasFocus);
-
         }
         }
 
 
         if (superViewOrParent is { })
         if (superViewOrParent is { })
@@ -684,7 +706,9 @@ public partial class View // Focus and cross-view navigation management (TabStop
         bool previousValue = HasFocus;
         bool previousValue = HasFocus;
 
 
         // Note, can't be cancelled.
         // Note, can't be cancelled.
-        NotifyFocusChanging (HasFocus, !HasFocus, newFocusedView, this);
+        NotifyFocusChanging (HasFocus, !HasFocus, this, newFocusedView);
+
+        Debug.Assert (_hasFocus);
 
 
         // Get whatever peer has focus, if any so we can update our superview's _previouslyMostFocused
         // Get whatever peer has focus, if any so we can update our superview's _previouslyMostFocused
         View? focusedPeer = superViewOrParent?.Focused;
         View? focusedPeer = superViewOrParent?.Focused;
@@ -692,16 +716,6 @@ public partial class View // Focus and cross-view navigation management (TabStop
         // Set HasFocus false
         // Set HasFocus false
         _hasFocus = false;
         _hasFocus = false;
 
 
-        if (Application.Navigation is { })
-        {
-            View? appFocused = Application.Navigation.GetFocused ();
-
-            if (appFocused is { } || appFocused == this)
-            {
-                Application.Navigation.SetFocused (newFocusedView ?? superViewOrParent);
-            }
-        }
-
         NotifyFocusChanged (HasFocus, this, newFocusedView);
         NotifyFocusChanged (HasFocus, this, newFocusedView);
 
 
         if (_hasFocus)
         if (_hasFocus)
@@ -721,6 +735,11 @@ public partial class View // Focus and cross-view navigation management (TabStop
 
 
     private void NotifyFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedVew)
     private void NotifyFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedVew)
     {
     {
+        if (newHasFocus && focusedVew?.Focused is null)
+        {
+            Application.Navigation?.SetFocused (focusedVew);
+        }
+
         // Call the virtual method
         // Call the virtual method
         OnHasFocusChanged (newHasFocus, previousFocusedView, focusedVew);
         OnHasFocusChanged (newHasFocus, previousFocusedView, focusedVew);
 
 

+ 189 - 122
Terminal.Gui/Views/HexView.cs

@@ -1,15 +1,17 @@
 #nullable enable
 #nullable enable
 
 
+using System.Diagnostics;
+
 //
 //
 // HexView.cs: A hexadecimal viewer
 // HexView.cs: A hexadecimal viewer
 //
 //
-// TODO:
-// - Support searching and highlighting of the search result
+// TODO: Support searching and highlighting of the search result
+// TODO: Support growing/shrinking the stream (e.g. del/backspace should work).
 // 
 // 
 
 
 namespace Terminal.Gui;
 namespace Terminal.Gui;
 
 
-/// <summary>An hex viewer and editor <see cref="View"/> over a <see cref="System.IO.Stream"/></summary>
+/// <summary>An hex viewer and editor <see cref="View"/> over a <see cref="Stream"/></summary>
 /// <remarks>
 /// <remarks>
 ///     <para>
 ///     <para>
 ///         <see cref="HexView"/> provides a hex editor on top of a seekable <see cref="Stream"/> with the left side
 ///         <see cref="HexView"/> provides a hex editor on top of a seekable <see cref="Stream"/> with the left side
@@ -28,11 +30,12 @@ namespace Terminal.Gui;
 /// </remarks>
 /// </remarks>
 public class HexView : View, IDesignable
 public class HexView : View, IDesignable
 {
 {
-    private const int BSIZE = 4;
-    private const int DISPLAY_WIDTH = 9;
+    private const int DEFAULT_ADDRESS_WIDTH = 8; // The default value for AddressWidth
+    private const int NUM_BYTES_PER_HEX_COLUMN = 4;
+    private const int HEX_COLUMN_WIDTH = NUM_BYTES_PER_HEX_COLUMN * 3 + 2; // 3 cols per byte + 1 for vert separator + right space
 
 
     private bool _firstNibble;
     private bool _firstNibble;
-    private bool _leftSide;
+    private bool _leftSideHasFocus;
     private static readonly Rune _spaceCharRune = new (' ');
     private static readonly Rune _spaceCharRune = new (' ');
     private static readonly Rune _periodCharRune = new ('.');
     private static readonly Rune _periodCharRune = new ('.');
 
 
@@ -47,7 +50,7 @@ public class HexView : View, IDesignable
 
 
         CanFocus = true;
         CanFocus = true;
         CursorVisibility = CursorVisibility.Default;
         CursorVisibility = CursorVisibility.Default;
-        _leftSide = true;
+        _leftSideHasFocus = true;
         _firstNibble = true;
         _firstNibble = true;
 
 
         // PERF: Closure capture of 'this' creates a lot of overhead.
         // PERF: Closure capture of 'this' creates a lot of overhead.
@@ -66,23 +69,20 @@ public class HexView : View, IDesignable
         AddCommand (Command.End, () => MoveEnd ());
         AddCommand (Command.End, () => MoveEnd ());
         AddCommand (Command.LeftStart, () => MoveLeftStart ());
         AddCommand (Command.LeftStart, () => MoveLeftStart ());
         AddCommand (Command.RightEnd, () => MoveEndOfLine ());
         AddCommand (Command.RightEnd, () => MoveEndOfLine ());
-        AddCommand (Command.StartOfPage, () => MoveUp (BytesPerLine * ((int)(position - _displayStart) / BytesPerLine)));
+        AddCommand (Command.StartOfPage, () => MoveUp (BytesPerLine * ((int)(Address - _displayStart) / BytesPerLine)));
 
 
         AddCommand (
         AddCommand (
                     Command.EndOfPage,
                     Command.EndOfPage,
-                    () => MoveDown (BytesPerLine * (Frame.Height - 1 - (int)(position - _displayStart) / BytesPerLine))
+                    () => MoveDown (BytesPerLine * (Frame.Height - 1 - (int)(Address - _displayStart) / BytesPerLine))
                    );
                    );
 
 
-        // Default keybindings for this view
         KeyBindings.Add (Key.CursorLeft, Command.Left);
         KeyBindings.Add (Key.CursorLeft, Command.Left);
         KeyBindings.Add (Key.CursorRight, Command.Right);
         KeyBindings.Add (Key.CursorRight, Command.Right);
         KeyBindings.Add (Key.CursorDown, Command.Down);
         KeyBindings.Add (Key.CursorDown, Command.Down);
         KeyBindings.Add (Key.CursorUp, Command.Up);
         KeyBindings.Add (Key.CursorUp, Command.Up);
 
 
-        KeyBindings.Add (Key.V.WithAlt, Command.PageUp);
         KeyBindings.Add (Key.PageUp, Command.PageUp);
         KeyBindings.Add (Key.PageUp, Command.PageUp);
 
 
-        KeyBindings.Add (Key.V.WithCtrl, Command.PageDown);
         KeyBindings.Add (Key.PageDown, Command.PageDown);
         KeyBindings.Add (Key.PageDown, Command.PageDown);
 
 
         KeyBindings.Add (Key.Home, Command.Start);
         KeyBindings.Add (Key.Home, Command.Start);
@@ -95,6 +95,9 @@ public class HexView : View, IDesignable
         KeyBindings.Add (Key.Tab, Command.Tab);
         KeyBindings.Add (Key.Tab, Command.Tab);
         KeyBindings.Add (Key.Tab.WithShift, Command.BackTab);
         KeyBindings.Add (Key.Tab.WithShift, Command.BackTab);
 
 
+        KeyBindings.Remove (Key.Space);
+        KeyBindings.Remove (Key.Enter);
+
         LayoutComplete += HexView_LayoutComplete;
         LayoutComplete += HexView_LayoutComplete;
     }
     }
 
 
@@ -102,30 +105,64 @@ public class HexView : View, IDesignable
     public HexView () : this (new MemoryStream ()) { }
     public HexView () : this (new MemoryStream ()) { }
 
 
     /// <summary>
     /// <summary>
-    ///     Gets or sets whether this <see cref="HexView"/> allow editing of the <see cref="Stream"/> of the underlying
+    ///     Gets or sets whether this <see cref="HexView"/> allows editing of the <see cref="Stream"/> of the underlying
     ///     <see cref="Stream"/>.
     ///     <see cref="Stream"/>.
     /// </summary>
     /// </summary>
-    /// <value><c>true</c> if allow edits; otherwise, <c>false</c>.</value>
+    /// <value><c>true</c> to allow edits; otherwise, <c>false</c>.</value>
     public bool AllowEdits { get; set; } = true;
     public bool AllowEdits { get; set; } = true;
 
 
-    /// <summary>Gets the current cursor position starting at one for both, line and column.</summary>
+    /// <summary>Gets the current cursor position.</summary>
     public Point CursorPosition
     public Point CursorPosition
     {
     {
         get
         get
         {
         {
-            if (!IsInitialized)
+            if (_source is null || BytesPerLine == 0)
             {
             {
                 return Point.Empty;
                 return Point.Empty;
             }
             }
+            var delta = (int)Address;
+
+            if (_leftSideHasFocus)
+            {
+                int line = delta / BytesPerLine;
+                int item = delta % BytesPerLine;
+
+                return new (item, line);
+            }
+            else
+            {
+                int line = delta / BytesPerLine;
+                int item = delta % BytesPerLine;
+
+                return new (item, line);
+            }
+        }
+    }
+
+
+    ///<inheritdoc/>
+    public override Point? PositionCursor ()
+    {
+        var delta = (int)(Address - _displayStart);
+        int line = delta / BytesPerLine;
+        int item = delta % BytesPerLine;
+        int block = item / NUM_BYTES_PER_HEX_COLUMN;
+        int column = item % NUM_BYTES_PER_HEX_COLUMN * 3;
 
 
-            var delta = (int)position;
-            int line = delta / BytesPerLine + 1;
-            int item = delta % BytesPerLine + 1;
+        int x = GetLeftSideStartColumn () + block * HEX_COLUMN_WIDTH + column + (_firstNibble ? 0 : 1);
+        int y = line;
 
 
-            return new (item, line);
+        if (!_leftSideHasFocus)
+        {
+            x = GetLeftSideStartColumn () + BytesPerLine / NUM_BYTES_PER_HEX_COLUMN * HEX_COLUMN_WIDTH + item - 1;
         }
         }
+
+        Move (x, y);
+
+        return new (x, y);
     }
     }
 
 
+
     private SortedDictionary<long, byte> _edits = [];
     private SortedDictionary<long, byte> _edits = [];
 
 
     /// <summary>
     /// <summary>
@@ -162,9 +199,9 @@ public class HexView : View, IDesignable
                 DisplayStart = 0;
                 DisplayStart = 0;
             }
             }
 
 
-            if (position > _source.Length)
+            if (Address > _source.Length)
             {
             {
-                position = 0;
+                Address = 0;
             }
             }
 
 
             SetNeedsDisplay ();
             SetNeedsDisplay ();
@@ -185,18 +222,22 @@ public class HexView : View, IDesignable
     }
     }
 
 
 
 
-    private long _pos;
+    private long _address;
 
 
-    // TODO: Why is this "starting at one"? How does that make sense?
-    /// <summary>Gets the current character position starting at one, related to the <see cref="Stream"/>.</summary>
-    public long Position => position + 1;
-
-    private long position
+    /// <summary>Gets or sets the current byte position in the <see cref="Stream"/>.</summary>
+    public long Address
     {
     {
-        get => _pos;
+        get => _address;
         set
         set
         {
         {
-            _pos = value;
+            if (_address == value)
+            {
+                return;
+            }
+
+            //ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual (value, Source!.Length, $"Position");
+
+            _address = value;
             RaisePositionChanged ();
             RaisePositionChanged ();
         }
         }
     }
     }
@@ -214,15 +255,39 @@ public class HexView : View, IDesignable
         get => _displayStart;
         get => _displayStart;
         set
         set
         {
         {
-            position = value;
+            Address = value;
 
 
             SetDisplayStart (value);
             SetDisplayStart (value);
         }
         }
     }
     }
 
 
+    private int _addressWidth = DEFAULT_ADDRESS_WIDTH;
+
+    /// <summary>
+    /// Gets or sets the width of the Address column on the left. Set to 0 to hide. The default is 8.
+    /// </summary>
+    public int AddressWidth
+    {
+        get => _addressWidth;
+        set
+        {
+            if (_addressWidth == value)
+            {
+                return;
+            }
+            _addressWidth = value;
+            SetNeedsDisplay ();
+        }
+    }
+
+    private int GetLeftSideStartColumn ()
+    {
+        return AddressWidth == 0 ? 0 : AddressWidth + 1;
+    }
+
     internal void SetDisplayStart (long value)
     internal void SetDisplayStart (long value)
     {
     {
-        if (value > 0 && value >= _source.Length)
+        if (value > 0 && value >= _source?.Length)
         {
         {
             _displayStart = _source.Length - 1;
             _displayStart = _source.Length - 1;
         }
         }
@@ -306,49 +371,49 @@ public class HexView : View, IDesignable
             return true;
             return true;
         }
         }
 
 
-        if (me.Position.X < DISPLAY_WIDTH)
+        if (me.Position.X < GetLeftSideStartColumn ())
         {
         {
             return true;
             return true;
         }
         }
 
 
-        int nblocks = BytesPerLine / BSIZE;
-        int blocksSize = nblocks * 14;
-        int blocksRightOffset = DISPLAY_WIDTH + blocksSize - 1;
+        int nblocks = BytesPerLine / NUM_BYTES_PER_HEX_COLUMN;
+        int blocksSize = nblocks * HEX_COLUMN_WIDTH;
+        int blocksRightOffset = GetLeftSideStartColumn () + blocksSize - 1;
 
 
         if (me.Position.X > blocksRightOffset + BytesPerLine - 1)
         if (me.Position.X > blocksRightOffset + BytesPerLine - 1)
         {
         {
             return true;
             return true;
         }
         }
 
 
-        _leftSide = me.Position.X >= blocksRightOffset;
+        bool clickIsOnLeftSide = me.Position.X >= blocksRightOffset;
         long lineStart = me.Position.Y * BytesPerLine + _displayStart;
         long lineStart = me.Position.Y * BytesPerLine + _displayStart;
-        int x = me.Position.X - DISPLAY_WIDTH + 1;
-        int block = x / 14;
+        int x = me.Position.X - GetLeftSideStartColumn () + 1;
+        int block = x / HEX_COLUMN_WIDTH;
         x -= block * 2;
         x -= block * 2;
         int empty = x % 3;
         int empty = x % 3;
         int item = x / 3;
         int item = x / 3;
 
 
-        if (!_leftSide && item > 0 && (empty == 0 || x == block * 14 + 14 - 1 - block * 2))
+        if (!clickIsOnLeftSide && item > 0 && (empty == 0 || x == block * HEX_COLUMN_WIDTH + HEX_COLUMN_WIDTH - 1 - block * 2))
         {
         {
             return true;
             return true;
         }
         }
 
 
         _firstNibble = true;
         _firstNibble = true;
 
 
-        if (_leftSide)
+        if (clickIsOnLeftSide)
         {
         {
-            position = Math.Min (lineStart + me.Position.X - blocksRightOffset, _source.Length);
+            Address = Math.Min (lineStart + me.Position.X - blocksRightOffset, _source.Length - 1);
         }
         }
         else
         else
         {
         {
-            position = Math.Min (lineStart + item, _source.Length);
+            Address = Math.Min (lineStart + item, _source.Length - 1);
         }
         }
 
 
         if (me.Flags == MouseFlags.Button1DoubleClicked)
         if (me.Flags == MouseFlags.Button1DoubleClicked)
         {
         {
-            _leftSide = !_leftSide;
+            _leftSideHasFocus = !clickIsOnLeftSide;
 
 
-            if (_leftSide)
+            if (_leftSideHasFocus)
             {
             {
                 _firstNibble = empty == 1;
                 _firstNibble = empty == 1;
             }
             }
@@ -376,8 +441,8 @@ public class HexView : View, IDesignable
         Driver.SetAttribute (current);
         Driver.SetAttribute (current);
         Move (0, 0);
         Move (0, 0);
 
 
-        int nblocks = BytesPerLine / BSIZE;
-        var data = new byte [nblocks * BSIZE * viewport.Height];
+        int nblocks = BytesPerLine / NUM_BYTES_PER_HEX_COLUMN;
+        var data = new byte [nblocks * NUM_BYTES_PER_HEX_COLUMN * viewport.Height];
         Source.Position = _displayStart;
         Source.Position = _displayStart;
         int n = _source.Read (data, 0, data.Length);
         int n = _source.Read (data, 0, data.Length);
 
 
@@ -396,20 +461,26 @@ public class HexView : View, IDesignable
             Move (0, line);
             Move (0, line);
             currentAttribute = GetHotNormalColor ();
             currentAttribute = GetHotNormalColor ();
             Driver.SetAttribute (currentAttribute);
             Driver.SetAttribute (currentAttribute);
-            Driver.AddStr ($"{_displayStart + line * nblocks * BSIZE:x8} ");
+            string address = $"{_displayStart + line * nblocks * NUM_BYTES_PER_HEX_COLUMN:x8}";
+            Driver.AddStr ($"{address.Substring (8 - AddressWidth)}");
+
+            if (AddressWidth > 0)
+            {
+                Driver.AddStr (" ");
+            }
 
 
             SetAttribute (GetNormalColor ());
             SetAttribute (GetNormalColor ());
 
 
             for (var block = 0; block < nblocks; block++)
             for (var block = 0; block < nblocks; block++)
             {
             {
-                for (var b = 0; b < BSIZE; b++)
+                for (var b = 0; b < NUM_BYTES_PER_HEX_COLUMN; b++)
                 {
                 {
-                    int offset = line * nblocks * BSIZE + block * BSIZE + b;
+                    int offset = line * nblocks * NUM_BYTES_PER_HEX_COLUMN + block * NUM_BYTES_PER_HEX_COLUMN + b;
                     byte value = GetData (data, offset, out bool edited);
                     byte value = GetData (data, offset, out bool edited);
 
 
-                    if (offset + _displayStart == position || edited)
+                    if (offset + _displayStart == Address || edited)
                     {
                     {
-                        SetAttribute (_leftSide ? activeColor : trackingColor);
+                        SetAttribute (_leftSideHasFocus ? activeColor : trackingColor);
                     }
                     }
                     else
                     else
                     {
                     {
@@ -424,9 +495,9 @@ public class HexView : View, IDesignable
                 Driver.AddStr (block + 1 == nblocks ? " " : "| ");
                 Driver.AddStr (block + 1 == nblocks ? " " : "| ");
             }
             }
 
 
-            for (var bitem = 0; bitem < nblocks * BSIZE; bitem++)
+            for (var bitem = 0; bitem < nblocks * NUM_BYTES_PER_HEX_COLUMN; bitem++)
             {
             {
-                int offset = line * nblocks * BSIZE + bitem;
+                int offset = line * nblocks * NUM_BYTES_PER_HEX_COLUMN + bitem;
                 byte b = GetData (data, offset, out bool edited);
                 byte b = GetData (data, offset, out bool edited);
                 Rune c;
                 Rune c;
 
 
@@ -450,9 +521,9 @@ public class HexView : View, IDesignable
                     }
                     }
                 }
                 }
 
 
-                if (offset + _displayStart == position || edited)
+                if (offset + _displayStart == Address || edited)
                 {
                 {
-                    SetAttribute (_leftSide ? trackingColor : activeColor);
+                    SetAttribute (_leftSideHasFocus ? trackingColor : activeColor);
                 }
                 }
                 else
                 else
                 {
                 {
@@ -492,13 +563,13 @@ public class HexView : View, IDesignable
     /// <summary>Raises the <see cref="PositionChanged"/> event.</summary>
     /// <summary>Raises the <see cref="PositionChanged"/> event.</summary>
     protected void RaisePositionChanged ()
     protected void RaisePositionChanged ()
     {
     {
-        HexViewEventArgs args = new (Position, CursorPosition, BytesPerLine);
+        HexViewEventArgs args = new (Address, CursorPosition, BytesPerLine);
         OnPositionChanged (args);
         OnPositionChanged (args);
         PositionChanged?.Invoke (this, args);
         PositionChanged?.Invoke (this, args);
     }
     }
 
 
     /// <summary>
     /// <summary>
-    ///     Called when <see cref="Position"/> has changed.
+    ///     Called when <see cref="Address"/> has changed.
     /// </summary>
     /// </summary>
     protected virtual void OnPositionChanged (HexViewEventArgs e) { }
     protected virtual void OnPositionChanged (HexViewEventArgs e) { }
 
 
@@ -519,7 +590,7 @@ public class HexView : View, IDesignable
             return false;
             return false;
         }
         }
 
 
-        if (_leftSide)
+        if (_leftSideHasFocus)
         {
         {
             int value;
             int value;
             var k = (char)keyEvent.KeyCode;
             var k = (char)keyEvent.KeyCode;
@@ -543,55 +614,51 @@ public class HexView : View, IDesignable
 
 
             byte b;
             byte b;
 
 
-            if (!_edits.TryGetValue (position, out b))
+            if (!_edits.TryGetValue (Address, out b))
             {
             {
-                _source.Position = position;
+                _source.Position = Address;
                 b = (byte)_source.ReadByte ();
                 b = (byte)_source.ReadByte ();
             }
             }
 
 
-            RedisplayLine (position);
+            // BUGBUG: This makes no sense here.
+            RedisplayLine (Address);
 
 
             if (_firstNibble)
             if (_firstNibble)
             {
             {
                 _firstNibble = false;
                 _firstNibble = false;
-                b = (byte)((b & 0xf) | (value << BSIZE));
-                _edits [position] = b;
-                RaiseEdited (new (position, _edits [position]));
+                b = (byte)((b & 0xf) | (value << NUM_BYTES_PER_HEX_COLUMN));
+                _edits [Address] = b;
+                RaiseEdited (new (Address, _edits [Address]));
             }
             }
             else
             else
             {
             {
                 b = (byte)((b & 0xf0) | value);
                 b = (byte)((b & 0xf0) | value);
-                _edits [position] = b;
-                RaiseEdited (new (position, _edits [position]));
+                _edits [Address] = b;
+                RaiseEdited (new (Address, _edits [Address]));
                 MoveRight ();
                 MoveRight ();
             }
             }
 
 
             return true;
             return true;
         }
         }
+        else
+        {
+            Rune r = keyEvent.AsRune;
 
 
-        return false;
-    }
+            // TODO: Enable entering Tab char - somehow disable Tab for navigation
 
 
-    ///<inheritdoc/>
-    public override Point? PositionCursor ()
-    {
-        var delta = (int)(position - _displayStart);
-        int line = delta / BytesPerLine;
-        int item = delta % BytesPerLine;
-        int block = item / BSIZE;
-        int column = item % BSIZE * 3;
+            _edits [Address] = (byte)(r.Value & 0x00FF);
+            MoveRight ();
 
 
-        int x = DISPLAY_WIDTH + block * 14 + column + (_firstNibble ? 0 : 1);
-        int y = line;
+            if ((byte)(r.Value & 0xFF00) > 0)
+            {
+                _edits [Address] = (byte)(r.Value & 0xFF00);
+                MoveRight ();
+            }
 
 
-        if (!_leftSide)
-        {
-            x = DISPLAY_WIDTH + BytesPerLine / BSIZE * 14 + item - 1;
+            //RaiseEdited (new (Address, _edits [Address]));
         }
         }
 
 
-        Move (x, y);
-
-        return new (x, y);
+        return false;
     }
     }
 
 
     //
     //
@@ -620,44 +687,44 @@ public class HexView : View, IDesignable
     private void HexView_LayoutComplete (object? sender, LayoutEventArgs e)
     private void HexView_LayoutComplete (object? sender, LayoutEventArgs e)
     {
     {
         // Small buffers will just show the position, with the bsize field value (4 bytes)
         // Small buffers will just show the position, with the bsize field value (4 bytes)
-        BytesPerLine = BSIZE;
+        BytesPerLine = NUM_BYTES_PER_HEX_COLUMN;
 
 
-        if (Viewport.Width - DISPLAY_WIDTH > 17)
+        if (Viewport.Width - GetLeftSideStartColumn () > 17)
         {
         {
-            BytesPerLine = BSIZE * ((Viewport.Width - DISPLAY_WIDTH) / 18);
+            BytesPerLine = NUM_BYTES_PER_HEX_COLUMN * ((Viewport.Width - GetLeftSideStartColumn ()) / 18);
         }
         }
     }
     }
 
 
     private bool MoveDown (int bytes)
     private bool MoveDown (int bytes)
     {
     {
-        RedisplayLine (position);
+        RedisplayLine (Address);
 
 
-        if (position + bytes < _source.Length)
+        if (Address + bytes < _source.Length)
         {
         {
-            position += bytes;
+            Address += bytes;
         }
         }
         else if ((bytes == BytesPerLine * Viewport.Height && _source.Length >= DisplayStart + BytesPerLine * Viewport.Height)
         else if ((bytes == BytesPerLine * Viewport.Height && _source.Length >= DisplayStart + BytesPerLine * Viewport.Height)
                  || (bytes <= BytesPerLine * Viewport.Height - BytesPerLine
                  || (bytes <= BytesPerLine * Viewport.Height - BytesPerLine
                      && _source.Length <= DisplayStart + BytesPerLine * Viewport.Height))
                      && _source.Length <= DisplayStart + BytesPerLine * Viewport.Height))
         {
         {
-            long p = position;
+            long p = Address;
 
 
             while (p + BytesPerLine < _source.Length)
             while (p + BytesPerLine < _source.Length)
             {
             {
                 p += BytesPerLine;
                 p += BytesPerLine;
             }
             }
 
 
-            position = p;
+            Address = p;
         }
         }
 
 
-        if (position >= DisplayStart + BytesPerLine * Viewport.Height)
+        if (Address >= DisplayStart + BytesPerLine * Viewport.Height)
         {
         {
             SetDisplayStart (DisplayStart + bytes);
             SetDisplayStart (DisplayStart + bytes);
             SetNeedsDisplay ();
             SetNeedsDisplay ();
         }
         }
         else
         else
         {
         {
-            RedisplayLine (position);
+            RedisplayLine (Address);
         }
         }
 
 
         return true;
         return true;
@@ -665,16 +732,16 @@ public class HexView : View, IDesignable
 
 
     private bool MoveEnd ()
     private bool MoveEnd ()
     {
     {
-        position = _source.Length;
+        Address = _source!.Length;
 
 
-        if (position >= DisplayStart + BytesPerLine * Viewport.Height)
+        if (Address >= DisplayStart + BytesPerLine * Viewport.Height)
         {
         {
-            SetDisplayStart (position);
+            SetDisplayStart (Address);
             SetNeedsDisplay ();
             SetNeedsDisplay ();
         }
         }
         else
         else
         {
         {
-            RedisplayLine (position);
+            RedisplayLine (Address);
         }
         }
 
 
         return true;
         return true;
@@ -682,7 +749,7 @@ public class HexView : View, IDesignable
 
 
     private bool MoveEndOfLine ()
     private bool MoveEndOfLine ()
     {
     {
-        position = Math.Min (position / BytesPerLine * BytesPerLine + BytesPerLine - 1, _source.Length);
+        Address = Math.Min (Address / BytesPerLine * BytesPerLine + BytesPerLine - 1, _source!.Length);
         SetNeedsDisplay ();
         SetNeedsDisplay ();
 
 
         return true;
         return true;
@@ -698,9 +765,9 @@ public class HexView : View, IDesignable
 
 
     private bool MoveLeft ()
     private bool MoveLeft ()
     {
     {
-        RedisplayLine (position);
+        RedisplayLine (Address);
 
 
-        if (_leftSide)
+        if (_leftSideHasFocus)
         {
         {
             if (!_firstNibble)
             if (!_firstNibble)
             {
             {
@@ -712,31 +779,31 @@ public class HexView : View, IDesignable
             _firstNibble = false;
             _firstNibble = false;
         }
         }
 
 
-        if (position == 0)
+        if (Address == 0)
         {
         {
             return true;
             return true;
         }
         }
 
 
-        if (position - 1 < DisplayStart)
+        if (Address - 1 < DisplayStart)
         {
         {
             SetDisplayStart (_displayStart - BytesPerLine);
             SetDisplayStart (_displayStart - BytesPerLine);
             SetNeedsDisplay ();
             SetNeedsDisplay ();
         }
         }
         else
         else
         {
         {
-            RedisplayLine (position);
+            RedisplayLine (Address);
         }
         }
 
 
-        position--;
+        Address--;
 
 
         return true;
         return true;
     }
     }
 
 
     private bool MoveRight ()
     private bool MoveRight ()
     {
     {
-        RedisplayLine (position);
+        RedisplayLine (Address);
 
 
-        if (_leftSide)
+        if (_leftSideHasFocus)
         {
         {
             if (_firstNibble)
             if (_firstNibble)
             {
             {
@@ -748,19 +815,19 @@ public class HexView : View, IDesignable
             _firstNibble = true;
             _firstNibble = true;
         }
         }
 
 
-        if (position < _source.Length)
+        if (Address < _source.Length - 1)
         {
         {
-            position++;
+            Address++;
         }
         }
 
 
-        if (position >= DisplayStart + BytesPerLine * Viewport.Height)
+        if (Address >= DisplayStart + BytesPerLine * Viewport.Height)
         {
         {
             SetDisplayStart (DisplayStart + BytesPerLine);
             SetDisplayStart (DisplayStart + BytesPerLine);
             SetNeedsDisplay ();
             SetNeedsDisplay ();
         }
         }
         else
         else
         {
         {
-            RedisplayLine (position);
+            RedisplayLine (Address);
         }
         }
 
 
         return true;
         return true;
@@ -768,7 +835,7 @@ public class HexView : View, IDesignable
 
 
     private bool MoveLeftStart ()
     private bool MoveLeftStart ()
     {
     {
-        position = position / BytesPerLine * BytesPerLine;
+        Address = Address / BytesPerLine * BytesPerLine;
         SetNeedsDisplay ();
         SetNeedsDisplay ();
 
 
         return true;
         return true;
@@ -776,21 +843,21 @@ public class HexView : View, IDesignable
 
 
     private bool MoveUp (int bytes)
     private bool MoveUp (int bytes)
     {
     {
-        RedisplayLine (position);
+        RedisplayLine (Address);
 
 
-        if (position - bytes > -1)
+        if (Address - bytes > -1)
         {
         {
-            position -= bytes;
+            Address -= bytes;
         }
         }
 
 
-        if (position < DisplayStart)
+        if (Address < DisplayStart)
         {
         {
             SetDisplayStart (DisplayStart - bytes);
             SetDisplayStart (DisplayStart - bytes);
             SetNeedsDisplay ();
             SetNeedsDisplay ();
         }
         }
         else
         else
         {
         {
-            RedisplayLine (position);
+            RedisplayLine (Address);
         }
         }
 
 
         return true;
         return true;
@@ -814,15 +881,15 @@ public class HexView : View, IDesignable
         switch (direction)
         switch (direction)
         {
         {
             case NavigationDirection.Forward:
             case NavigationDirection.Forward:
-                _leftSide = !_leftSide;
-                RedisplayLine (position);
+                _leftSideHasFocus = !_leftSideHasFocus;
+                RedisplayLine (Address);
                 _firstNibble = true;
                 _firstNibble = true;
 
 
                 return true;
                 return true;
 
 
             case NavigationDirection.Backward:
             case NavigationDirection.Backward:
-                _leftSide = !_leftSide;
-                RedisplayLine (position);
+                _leftSideHasFocus = !_leftSideHasFocus;
+                RedisplayLine (Address);
                 _firstNibble = true;
                 _firstNibble = true;
 
 
                 return true;
                 return true;

+ 11 - 11
Terminal.Gui/Views/HexViewEventArgs.cs

@@ -12,12 +12,12 @@ namespace Terminal.Gui;
 public class HexViewEventArgs : EventArgs
 public class HexViewEventArgs : EventArgs
 {
 {
     /// <summary>Initializes a new instance of <see cref="HexViewEventArgs"/></summary>
     /// <summary>Initializes a new instance of <see cref="HexViewEventArgs"/></summary>
-    /// <param name="pos">The character position.</param>
+    /// <param name="address">The byte position in the steam.</param>
     /// <param name="cursor">The cursor position.</param>
     /// <param name="cursor">The cursor position.</param>
     /// <param name="lineLength">Line bytes length.</param>
     /// <param name="lineLength">Line bytes length.</param>
-    public HexViewEventArgs (long pos, Point cursor, int lineLength)
+    public HexViewEventArgs (long address, Point cursor, int lineLength)
     {
     {
-        Position = pos;
+        Address = address;
         CursorPosition = cursor;
         CursorPosition = cursor;
         BytesPerLine = lineLength;
         BytesPerLine = lineLength;
     }
     }
@@ -28,25 +28,25 @@ public class HexViewEventArgs : EventArgs
     /// <summary>Gets the current cursor position starting at one for both, line and column.</summary>
     /// <summary>Gets the current cursor position starting at one for both, line and column.</summary>
     public Point CursorPosition { get; private set; }
     public Point CursorPosition { get; private set; }
 
 
-    /// <summary>Gets the current character position starting at one, related to the <see cref="Stream"/>.</summary>
-    public long Position { get; private set; }
+    /// <summary>Gets the byte position in the <see cref="Stream"/>.</summary>
+    public long Address { get; private set; }
 }
 }
 
 
 /// <summary>Defines the event arguments for <see cref="HexView.Edited"/> event.</summary>
 /// <summary>Defines the event arguments for <see cref="HexView.Edited"/> event.</summary>
 public class HexViewEditEventArgs : EventArgs
 public class HexViewEditEventArgs : EventArgs
 {
 {
     /// <summary>Creates a new instance of the <see cref="HexViewEditEventArgs"/> class.</summary>
     /// <summary>Creates a new instance of the <see cref="HexViewEditEventArgs"/> class.</summary>
-    /// <param name="position"></param>
+    /// <param name="address"></param>
     /// <param name="newValue"></param>
     /// <param name="newValue"></param>
-    public HexViewEditEventArgs (long position, byte newValue)
+    public HexViewEditEventArgs (long address, byte newValue)
     {
     {
-        Position = position;
+        Address = address;
         NewValue = newValue;
         NewValue = newValue;
     }
     }
 
 
-    /// <summary>Gets the new value for that <see cref="Position"/>.</summary>
+    /// <summary>Gets the new value for that <see cref="Address"/>.</summary>
     public byte NewValue { get; }
     public byte NewValue { get; }
 
 
-    /// <summary>Gets the location of the edit.</summary>
-    public long Position { get; }
+    /// <summary>Gets the adress of the edit in the stream.</summary>
+    public long Address { get; }
 }
 }

+ 1 - 1
Terminal.Gui/Views/Shortcut.cs

@@ -58,7 +58,7 @@ public class Shortcut : View, IOrientation, IDesignable
     ///     </para>
     ///     </para>
     /// </remarks>
     /// </remarks>
     /// <param name="targetView">
     /// <param name="targetView">
-    ///     The View that <paramref name="command"/> will be invoked when user does something that causes the Shortcut's Accept
+    ///     The View that <paramref name="command"/> will be invoked on when user does something that causes the Shortcut's Accept
     ///     event to be raised.
     ///     event to be raised.
     /// </param>
     /// </param>
     /// <param name="command">
     /// <param name="command">

+ 111 - 72
UICatalog/Scenarios/HexEditor.cs

@@ -16,27 +16,30 @@ public class HexEditor : Scenario
     private HexView _hexView;
     private HexView _hexView;
     private MenuItem _miAllowEdits;
     private MenuItem _miAllowEdits;
     private bool _saved = true;
     private bool _saved = true;
-    private Shortcut _siPositionChanged;
+    private Shortcut _scAddress;
+    private Shortcut _scInfo;
+    private Shortcut _scPosition;
     private StatusBar _statusBar;
     private StatusBar _statusBar;
 
 
     public override void Main ()
     public override void Main ()
     {
     {
         Application.Init ();
         Application.Init ();
-        Toplevel app = new Toplevel ()
+
+        var app = new Toplevel
         {
         {
             ColorScheme = Colors.ColorSchemes ["Base"]
             ColorScheme = Colors.ColorSchemes ["Base"]
         };
         };
 
 
         CreateDemoFile (_fileName);
         CreateDemoFile (_fileName);
 
 
-        _hexView = new HexView (new MemoryStream (Encoding.UTF8.GetBytes ("Demo text.")))
+        _hexView = new (new MemoryStream (Encoding.UTF8.GetBytes ("Demo text.")))
         {
         {
             X = 0,
             X = 0,
             Y = 1,
             Y = 1,
             Width = Dim.Fill (),
             Width = Dim.Fill (),
             Height = Dim.Fill (1),
             Height = Dim.Fill (1),
             Title = _fileName ?? "Untitled",
             Title = _fileName ?? "Untitled",
-            BorderStyle = LineStyle.Rounded,
+            BorderStyle = LineStyle.Rounded
         };
         };
         _hexView.Edited += _hexView_Edited;
         _hexView.Edited += _hexView_Edited;
         _hexView.PositionChanged += _hexView_PositionChanged;
         _hexView.PositionChanged += _hexView_PositionChanged;
@@ -46,71 +49,97 @@ public class HexEditor : Scenario
         {
         {
             Menus =
             Menus =
             [
             [
-                new MenuBarItem (
-                                 "_File",
-                                 new MenuItem []
-                                 {
-                                     new ("_New", "", () => New ()),
-                                     new ("_Open", "", () => Open ()),
-                                     new ("_Save", "", () => Save ()),
-                                     null,
-                                     new ("_Quit", "", () => Quit ())
-                                 }
-                                ),
-                new MenuBarItem (
-                                 "_Edit",
-                                 new MenuItem []
-                                 {
-                                     new ("_Copy", "", () => Copy ()),
-                                     new ("C_ut", "", () => Cut ()),
-                                     new ("_Paste", "", () => Paste ())
-                                 }
-                                ),
-                new MenuBarItem (
-                                 "_Options",
-                                 new []
-                                 {
-                                     _miAllowEdits = new MenuItem (
-                                                                   "_AllowEdits",
-                                                                   "",
-                                                                   () => ToggleAllowEdits ()
-                                                                  )
-                                     {
-                                         Checked = _hexView.AllowEdits,
-                                         CheckType = MenuItemCheckStyle
-                                             .Checked
-                                     }
-                                 }
-                                )
+                new (
+                     "_File",
+                     new MenuItem []
+                     {
+                         new ("_New", "", () => New ()),
+                         new ("_Open", "", () => Open ()),
+                         new ("_Save", "", () => Save ()),
+                         null,
+                         new ("_Quit", "", () => Quit ())
+                     }
+                    ),
+                new (
+                     "_Edit",
+                     new MenuItem []
+                     {
+                         new ("_Copy", "", () => Copy ()),
+                         new ("C_ut", "", () => Cut ()),
+                         new ("_Paste", "", () => Paste ())
+                     }
+                    ),
+                new (
+                     "_Options",
+                     new []
+                     {
+                         _miAllowEdits = new (
+                                              "_AllowEdits",
+                                              "",
+                                              () => ToggleAllowEdits ()
+                                             )
+                         {
+                             Checked = _hexView.AllowEdits,
+                             CheckType = MenuItemCheckStyle
+                                 .Checked
+                         }
+                     }
+                    )
             ]
             ]
         };
         };
         app.Add (menu);
         app.Add (menu);
 
 
-        _statusBar = new StatusBar (
-                                    new []
-                                    {
-                                        new (Key.F2, "Open", () => Open ()),
-                                        new (Key.F3, "Save", () => Save ()),
-                                        new (
-                                             Application.QuitKey,
-                                             $"Quit",
-                                             () => Quit ()
-                                            ),
-                                        _siPositionChanged = new Shortcut (
-                                                                             Key.Empty,
-                                                                             $"Position: {
-                                                                                 _hexView.Position
-                                                                             } Line: {
-                                                                                 _hexView.CursorPosition.Y
-                                                                             } Col: {
-                                                                                 _hexView.CursorPosition.X
-                                                                             } Line length: {
-                                                                                 _hexView.BytesPerLine
-                                                                             }",
-                                                                             () => { }
-                                                                            )
-                                    }
-                                   )
+        var addressWidthUpDown = new NumericUpDown
+        {
+            Value = _hexView.AddressWidth
+        };
+
+        NumericUpDown<long> addressUpDown = new NumericUpDown<long>
+        {
+            Value = _hexView.Address,
+            Format = $"0x{{0:X{_hexView.AddressWidth}}}"
+        };
+
+        addressWidthUpDown.ValueChanging += (sender, args) =>
+                                            {
+                                                args.Cancel = args.NewValue is < 0 or > 8;
+
+                                                if (!args.Cancel)
+                                                {
+                                                    _hexView.AddressWidth = args.NewValue;
+
+                                                    // ReSharper disable once AccessToDisposedClosure
+                                                    addressUpDown.Format = $"0x{{0:X{_hexView.AddressWidth}}}";
+                                                }
+                                            };
+
+        addressUpDown.ValueChanging += (sender, args) =>
+                                       {
+                                           args.Cancel = args.NewValue is < 0;
+
+                                           if (!args.Cancel)
+                                           {
+                                               _hexView.Address = args.NewValue;
+                                           }
+                                       };
+
+        _statusBar = new (
+                          [
+                              new (Key.F2, "Open", Open),
+                              new (Key.F3, "Save", Save),
+                              new ()
+                              {
+                                  CommandView = addressWidthUpDown,
+                                  HelpText = "Address Width"
+                              },
+                              _scAddress = new ()
+                              {
+                                  CommandView = addressUpDown,
+                                  HelpText = "Address:"
+                              },
+                              _scInfo = new (Key.Empty, string.Empty, () => { }),
+                              _scPosition = new (Key.Empty, string.Empty, () => { })
+                          ])
         {
         {
             AlignmentModes = AlignmentModes.IgnoreFirstOrLast
             AlignmentModes = AlignmentModes.IgnoreFirstOrLast
         };
         };
@@ -119,6 +148,8 @@ public class HexEditor : Scenario
         _hexView.Source = LoadFile ();
         _hexView.Source = LoadFile ();
 
 
         Application.Run (app);
         Application.Run (app);
+        addressUpDown.Dispose ();
+        addressWidthUpDown.Dispose ();
         app.Dispose ();
         app.Dispose ();
         Application.Shutdown ();
         Application.Shutdown ();
     }
     }
@@ -127,8 +158,15 @@ public class HexEditor : Scenario
 
 
     private void _hexView_PositionChanged (object sender, HexViewEventArgs obj)
     private void _hexView_PositionChanged (object sender, HexViewEventArgs obj)
     {
     {
-        _siPositionChanged.Title =
-            $"Position: {obj.Position} Line: {obj.CursorPosition.Y} Col: {obj.CursorPosition.X} Line length: {obj.BytesPerLine}";
+        _scInfo.Title =
+            $"Bytes: {_hexView.Source!.Length}";
+        _scPosition.Title =
+            $"L: {obj.CursorPosition.Y} C: {obj.CursorPosition.X} Per Line: {obj.BytesPerLine}";
+
+        if (_scAddress.CommandView is NumericUpDown<long> addrNumericUpDown)
+        {
+            addrNumericUpDown.Value = obj.Address;
+        }
     }
     }
 
 
     private void Copy () { MessageBox.ErrorQuery ("Not Implemented", "Functionality not yet implemented.", "Ok"); }
     private void Copy () { MessageBox.ErrorQuery ("Not Implemented", "Functionality not yet implemented.", "Ok"); }
@@ -147,7 +185,7 @@ public class HexEditor : Scenario
     private void CreateUnicodeDemoFile (string fileName)
     private void CreateUnicodeDemoFile (string fileName)
     {
     {
         var sb = new StringBuilder ();
         var sb = new StringBuilder ();
-        sb.Append ("Hello world.\n");
+        sb.Append ("Hello world with wide codepoints: 𝔹Aℝ𝔽.\n");
         sb.Append ("This is a test of the Emergency Broadcast System.\n");
         sb.Append ("This is a test of the Emergency Broadcast System.\n");
 
 
         byte [] buffer = Encoding.Unicode.GetBytes (sb.ToString ());
         byte [] buffer = Encoding.Unicode.GetBytes (sb.ToString ());
@@ -169,8 +207,8 @@ public class HexEditor : Scenario
             if (MessageBox.ErrorQuery (
             if (MessageBox.ErrorQuery (
                                        "Save",
                                        "Save",
                                        "The changes were not saved. Want to open without saving?",
                                        "The changes were not saved. Want to open without saving?",
-                                       "Yes",
-                                       "No"
+                                       "_Yes",
+                                       "_No"
                                       )
                                       )
                 == 1)
                 == 1)
             {
             {
@@ -190,7 +228,7 @@ public class HexEditor : Scenario
         }
         }
         else
         else
         {
         {
-            _hexView.Title = (_fileName ?? "Untitled");
+            _hexView.Title = _fileName ?? "Untitled";
         }
         }
 
 
         return stream;
         return stream;
@@ -213,10 +251,11 @@ public class HexEditor : Scenario
             _hexView.Source = LoadFile ();
             _hexView.Source = LoadFile ();
             _hexView.DisplayStart = 0;
             _hexView.DisplayStart = 0;
         }
         }
+
         d.Dispose ();
         d.Dispose ();
     }
     }
 
 
-    private void Paste () { MessageBox.ErrorQuery ("Not Implemented", "Functionality not yet implemented.", "Ok"); }
+    private void Paste () { MessageBox.ErrorQuery ("Not Implemented", "Functionality not yet implemented.", "_Ok"); }
     private void Quit () { Application.RequestStop (); }
     private void Quit () { Application.RequestStop (); }
 
 
     private void Save ()
     private void Save ()

+ 1 - 0
UICatalog/UICatalog.cs

@@ -387,6 +387,7 @@ public class UICatalogApp
         // 'app' closed cleanly.
         // 'app' closed cleanly.
         foreach (Responder? inst in Responder.Instances)
         foreach (Responder? inst in Responder.Instances)
         {
         {
+            
             Debug.Assert (inst.WasDisposed);
             Debug.Assert (inst.WasDisposed);
         }
         }
 
 

+ 3 - 3
UnitTests/Application/Application.NavigationTests.cs

@@ -68,7 +68,7 @@ public class ApplicationNavigationTests (ITestOutputHelper output)
 
 
         Application.Navigation.FocusedChanged += ApplicationNavigationOnFocusedChanged;
         Application.Navigation.FocusedChanged += ApplicationNavigationOnFocusedChanged;
 
 
-        Application.Navigation.SetFocused (new ());
+        Application.Navigation.SetFocused (new () { CanFocus = true, HasFocus = true });
 
 
         Assert.True (raised);
         Assert.True (raised);
 
 
@@ -89,7 +89,7 @@ public class ApplicationNavigationTests (ITestOutputHelper output)
     {
     {
         Application.Navigation = new ();
         Application.Navigation = new ();
 
 
-        Application.Top = new()
+        Application.Top = new ()
         {
         {
             Id = "top",
             Id = "top",
             CanFocus = true
             CanFocus = true
@@ -125,7 +125,7 @@ public class ApplicationNavigationTests (ITestOutputHelper output)
     {
     {
         Application.Navigation = new ();
         Application.Navigation = new ();
 
 
-        Application.Top = new()
+        Application.Top = new ()
         {
         {
             Id = "top",
             Id = "top",
             CanFocus = true
             CanFocus = true

+ 150 - 110
UnitTests/Views/HexViewTests.cs

@@ -1,13 +1,42 @@
-using System.Text;
+#nullable enable
+using System.Text;
+using JetBrains.Annotations;
 
 
 namespace Terminal.Gui.ViewsTests;
 namespace Terminal.Gui.ViewsTests;
 
 
 public class HexViewTests
 public class HexViewTests
 {
 {
+    [Theory]
+    [InlineData (0, 4)]
+    [InlineData (9, 4)]
+    [InlineData (20, 4)]
+    [InlineData (24, 4)]
+    [InlineData (30, 4)]
+    [InlineData (50, 4)]
+    public void BytesPerLine_Calculates_Correctly (int width, int expectedBPL)
+    {
+        var hv = new HexView (LoadStream (null, out long _)) { Width = width, Height = 10 };
+        hv.LayoutSubviews ();
+
+        Assert.Equal (expectedBPL, hv.BytesPerLine);
+    }
+
+    [Theory]
+    [InlineData ("01234", 20, 4)]
+    [InlineData ("012345", 20, 4)]
+    public void xuz (string str, int width, int expectedBPL)
+    {
+        var hv = new HexView (LoadStream (str, out long _)) { Width = width, Height = 10 };
+        hv.LayoutSubviews ();
+
+        Assert.Equal (expectedBPL, hv.BytesPerLine);
+    }
+
+
     [Fact]
     [Fact]
     public void AllowEdits_Edits_ApplyEdits ()
     public void AllowEdits_Edits_ApplyEdits ()
     {
     {
-        var hv = new HexView (LoadStream (true)) { Width = 20, Height = 20 };
+        var hv = new HexView (LoadStream (null, out _, true)) { Width = 20, Height = 20 };
 
 
         // Needed because HexView relies on LayoutComplete to calc sizes
         // Needed because HexView relies on LayoutComplete to calc sizes
         hv.LayoutSubviews ();
         hv.LayoutSubviews ();
@@ -94,45 +123,50 @@ public class HexViewTests
     }
     }
 
 
     [Fact]
     [Fact]
-    [AutoInitShutdown]
     public void CursorPosition_Encoding_Default ()
     public void CursorPosition_Encoding_Default ()
     {
     {
-        var hv = new HexView (LoadStream ()) { Width = Dim.Fill (), Height = Dim.Fill () };
-        var top = new Toplevel ();
-        top.Add (hv);
-        Application.Begin (top);
+        var hv = new HexView (LoadStream (null, out _)) { Width = 100, Height = 100 };
+        Application.Top = new Toplevel ();
+        Application.Top.Add (hv);
 
 
-        Assert.Equal (new (1, 1), hv.CursorPosition);
+        Application.Top.LayoutSubviews ();
+
+        Assert.Equal (new (0, 0), hv.CursorPosition);
+        Assert.Equal (20, hv.BytesPerLine);
 
 
         Assert.True (hv.NewKeyDownEvent (Key.Tab));
         Assert.True (hv.NewKeyDownEvent (Key.Tab));
+        Assert.Equal (new (0, 0), hv.CursorPosition);
+
         Assert.True (hv.NewKeyDownEvent (Key.CursorRight.WithCtrl));
         Assert.True (hv.NewKeyDownEvent (Key.CursorRight.WithCtrl));
-        Assert.Equal (hv.CursorPosition.X, hv.BytesPerLine);
+        Assert.Equal (hv.CursorPosition.X, hv.BytesPerLine - 1);
         Assert.True (hv.NewKeyDownEvent (Key.Home));
         Assert.True (hv.NewKeyDownEvent (Key.Home));
 
 
         Assert.True (hv.NewKeyDownEvent (Key.CursorRight));
         Assert.True (hv.NewKeyDownEvent (Key.CursorRight));
-        Assert.Equal (new (2, 1), hv.CursorPosition);
+        Assert.Equal (new (1, 0), hv.CursorPosition);
 
 
         Assert.True (hv.NewKeyDownEvent (Key.CursorDown));
         Assert.True (hv.NewKeyDownEvent (Key.CursorDown));
-        Assert.Equal (new (2, 2), hv.CursorPosition);
+        Assert.Equal (new (1, 1), hv.CursorPosition);
 
 
         Assert.True (hv.NewKeyDownEvent (Key.End));
         Assert.True (hv.NewKeyDownEvent (Key.End));
+        Assert.Equal (new (2, 2), hv.CursorPosition);
         int col = hv.CursorPosition.X;
         int col = hv.CursorPosition.X;
         int line = hv.CursorPosition.Y;
         int line = hv.CursorPosition.Y;
-        int offset = (line - 1) * (hv.BytesPerLine - col);
-        Assert.Equal (hv.Position, col * line + offset);
-        top.Dispose ();
+        int offset = line * (hv.BytesPerLine - col);
+        Assert.Equal (hv.Address, col * line + offset);
+        Application.Top.Dispose ();
+        Application.ResetState (true);
     }
     }
 
 
     [Fact]
     [Fact]
-    [AutoInitShutdown]
     public void CursorPosition_Encoding_Unicode ()
     public void CursorPosition_Encoding_Unicode ()
     {
     {
-        var hv = new HexView (LoadStream (true)) { Width = Dim.Fill (), Height = Dim.Fill () };
-        var top = new Toplevel ();
-        top.Add (hv);
-        Application.Begin (top);
+        var hv = new HexView (LoadStream (null, out _, true)) { Width = Dim.Fill (), Height = Dim.Fill () };
+        Application.Top = new Toplevel ();
+        Application.Top.Add (hv);
 
 
-        Assert.Equal (new (1, 1), hv.CursorPosition);
+        hv.LayoutSubviews ();
+
+        Assert.Equal (new (0, 0), hv.CursorPosition);
 
 
         Assert.True (hv.NewKeyDownEvent (Key.Tab));
         Assert.True (hv.NewKeyDownEvent (Key.Tab));
         Assert.True (hv.NewKeyDownEvent (Key.CursorRight.WithCtrl));
         Assert.True (hv.NewKeyDownEvent (Key.CursorRight.WithCtrl));
@@ -149,14 +183,15 @@ public class HexViewTests
         int col = hv.CursorPosition.X;
         int col = hv.CursorPosition.X;
         int line = hv.CursorPosition.Y;
         int line = hv.CursorPosition.Y;
         int offset = (line - 1) * (hv.BytesPerLine - col);
         int offset = (line - 1) * (hv.BytesPerLine - col);
-        Assert.Equal (hv.Position, col * line + offset);
-        top.Dispose ();
+        Assert.Equal (hv.Address, col * line + offset);
+        Application.Top.Dispose ();
+        Application.ResetState (true);
     }
     }
 
 
     [Fact]
     [Fact]
     public void DiscardEdits_Method ()
     public void DiscardEdits_Method ()
     {
     {
-        var hv = new HexView (LoadStream (true)) { Width = 20, Height = 20 };
+        var hv = new HexView (LoadStream (null, out _, true)) { Width = 20, Height = 20 };
 
 
         // Needed because HexView relies on LayoutComplete to calc sizes
         // Needed because HexView relies on LayoutComplete to calc sizes
         hv.LayoutSubviews ();
         hv.LayoutSubviews ();
@@ -175,7 +210,7 @@ public class HexViewTests
     [Fact]
     [Fact]
     public void DisplayStart_Source ()
     public void DisplayStart_Source ()
     {
     {
-        var hv = new HexView (LoadStream (true)) { Width = 20, Height = 20 };
+        var hv = new HexView (LoadStream (null, out _, true)) { Width = 20, Height = 20 };
 
 
         // Needed because HexView relies on LayoutComplete to calc sizes
         // Needed because HexView relies on LayoutComplete to calc sizes
         hv.LayoutSubviews ();
         hv.LayoutSubviews ();
@@ -196,13 +231,13 @@ public class HexViewTests
     [Fact]
     [Fact]
     public void Edited_Event ()
     public void Edited_Event ()
     {
     {
-        var hv = new HexView (LoadStream (true)) { Width = 20, Height = 20 };
+        var hv = new HexView (LoadStream (null, out _, true)) { Width = 20, Height = 20 };
 
 
         // Needed because HexView relies on LayoutComplete to calc sizes
         // Needed because HexView relies on LayoutComplete to calc sizes
         hv.LayoutSubviews ();
         hv.LayoutSubviews ();
 
 
         KeyValuePair<long, byte> keyValuePair = default;
         KeyValuePair<long, byte> keyValuePair = default;
-        hv.Edited += (s, e) => keyValuePair = new (e.Position, e.NewValue);
+        hv.Edited += (s, e) => keyValuePair = new (e.Address, e.NewValue);
 
 
         Assert.True (hv.NewKeyDownEvent (Key.D4));
         Assert.True (hv.NewKeyDownEvent (Key.D4));
         Assert.True (hv.NewKeyDownEvent (Key.D6));
         Assert.True (hv.NewKeyDownEvent (Key.D6));
@@ -220,143 +255,142 @@ public class HexViewTests
     }
     }
 
 
     [Fact]
     [Fact]
-    [AutoInitShutdown]
-    public void KeyBindings_Command ()
+    public void KeyBindings_Test_Movement_LeftSide ()
     {
     {
-        var hv = new HexView (LoadStream ()) { Width = 20, Height = 10 };
-        var top = new Toplevel ();
-        top.Add (hv);
-        Application.Begin (top);
+        var hv = new HexView (LoadStream (null, out _)) { Width = 20, Height = 10 };
+        Application.Top = new Toplevel ();
+        Application.Top.Add (hv);
 
 
-        Assert.Equal (63, hv.Source.Length);
-        Assert.Equal (1, hv.Position);
+        hv.LayoutSubviews ();
+
+        Assert.Equal (MEM_STRING_LENGTH, hv.Source.Length);
+        Assert.Equal (0, hv.Address);
         Assert.Equal (4, hv.BytesPerLine);
         Assert.Equal (4, hv.BytesPerLine);
 
 
         // right side only needed to press one time
         // right side only needed to press one time
         Assert.True (hv.NewKeyDownEvent (Key.Tab));
         Assert.True (hv.NewKeyDownEvent (Key.Tab));
 
 
         Assert.True (hv.NewKeyDownEvent (Key.CursorRight));
         Assert.True (hv.NewKeyDownEvent (Key.CursorRight));
-        Assert.Equal (2, hv.Position);
+        Assert.Equal (1, hv.Address);
 
 
         Assert.True (hv.NewKeyDownEvent (Key.CursorLeft));
         Assert.True (hv.NewKeyDownEvent (Key.CursorLeft));
-        Assert.Equal (1, hv.Position);
+        Assert.Equal (0, hv.Address);
 
 
         Assert.True (hv.NewKeyDownEvent (Key.CursorDown));
         Assert.True (hv.NewKeyDownEvent (Key.CursorDown));
-        Assert.Equal (5, hv.Position);
+        Assert.Equal (4, hv.Address);
 
 
         Assert.True (hv.NewKeyDownEvent (Key.CursorUp));
         Assert.True (hv.NewKeyDownEvent (Key.CursorUp));
-        Assert.Equal (1, hv.Position);
-
-        Assert.True (hv.NewKeyDownEvent (Key.V.WithCtrl));
-        Assert.Equal (41, hv.Position);
-
-        Assert.True (hv.NewKeyDownEvent (new (Key.V.WithAlt)));
-        Assert.Equal (1, hv.Position);
+        Assert.Equal (0, hv.Address);
 
 
         Assert.True (hv.NewKeyDownEvent (Key.PageDown));
         Assert.True (hv.NewKeyDownEvent (Key.PageDown));
-        Assert.Equal (41, hv.Position);
+        Assert.Equal (40, hv.Address);
 
 
         Assert.True (hv.NewKeyDownEvent (Key.PageUp));
         Assert.True (hv.NewKeyDownEvent (Key.PageUp));
-        Assert.Equal (1, hv.Position);
+        Assert.Equal (0, hv.Address);
 
 
         Assert.True (hv.NewKeyDownEvent (Key.End));
         Assert.True (hv.NewKeyDownEvent (Key.End));
-        Assert.Equal (64, hv.Position);
+        Assert.Equal (MEM_STRING_LENGTH, hv.Address);
 
 
         Assert.True (hv.NewKeyDownEvent (Key.Home));
         Assert.True (hv.NewKeyDownEvent (Key.Home));
-        Assert.Equal (1, hv.Position);
+        Assert.Equal (0, hv.Address);
 
 
         Assert.True (hv.NewKeyDownEvent (Key.CursorRight.WithCtrl));
         Assert.True (hv.NewKeyDownEvent (Key.CursorRight.WithCtrl));
-        Assert.Equal (4, hv.Position);
+        Assert.Equal (3, hv.Address);
 
 
         Assert.True (hv.NewKeyDownEvent (Key.CursorLeft.WithCtrl));
         Assert.True (hv.NewKeyDownEvent (Key.CursorLeft.WithCtrl));
-        Assert.Equal (1, hv.Position);
+        Assert.Equal (0, hv.Address);
 
 
         Assert.True (hv.NewKeyDownEvent (Key.CursorDown.WithCtrl));
         Assert.True (hv.NewKeyDownEvent (Key.CursorDown.WithCtrl));
-        Assert.Equal (37, hv.Position);
+        Assert.Equal (36, hv.Address);
 
 
         Assert.True (hv.NewKeyDownEvent (Key.CursorUp.WithCtrl));
         Assert.True (hv.NewKeyDownEvent (Key.CursorUp.WithCtrl));
-        Assert.Equal (1, hv.Position);
-        top.Dispose ();
+        Assert.Equal (0, hv.Address);
+        Application.Top.Dispose ();
+        Application.ResetState (true);
     }
     }
 
 
     [Fact]
     [Fact]
     public void Position_Using_Encoding_Default ()
     public void Position_Using_Encoding_Default ()
     {
     {
-        var hv = new HexView (LoadStream ()) { Width = 20, Height = 20 };
+        var hv = new HexView (LoadStream (null, out _)) { Width = 20, Height = 20 };
+        hv.LayoutSubviews ();
 
 
         // Needed because HexView relies on LayoutComplete to calc sizes
         // Needed because HexView relies on LayoutComplete to calc sizes
         hv.LayoutSubviews ();
         hv.LayoutSubviews ();
-        Assert.Equal (63, hv.Source.Length);
-        Assert.Equal (63, hv.Source.Position);
-        Assert.Equal (1, hv.Position);
+        Assert.Equal (MEM_STRING_LENGTH, hv.Source.Length);
+        Assert.Equal (MEM_STRING_LENGTH, hv.Source.Position);
+        Assert.Equal (0, hv.Address);
 
 
         // left side needed to press twice
         // left side needed to press twice
         Assert.True (hv.NewKeyDownEvent (Key.CursorRight));
         Assert.True (hv.NewKeyDownEvent (Key.CursorRight));
-        Assert.Equal (63, hv.Source.Position);
-        Assert.Equal (1, hv.Position);
+        Assert.Equal (MEM_STRING_LENGTH, hv.Source.Position);
+        Assert.Equal (1, hv.Address);
         Assert.True (hv.NewKeyDownEvent (Key.CursorRight));
         Assert.True (hv.NewKeyDownEvent (Key.CursorRight));
-        Assert.Equal (63, hv.Source.Position);
-        Assert.Equal (2, hv.Position);
+        Assert.Equal (MEM_STRING_LENGTH, hv.Source.Position);
+        Assert.Equal (2, hv.Address);
 
 
         // right side only needed to press one time
         // right side only needed to press one time
         Assert.True (hv.NewKeyDownEvent (Key.Tab));
         Assert.True (hv.NewKeyDownEvent (Key.Tab));
-        Assert.Equal (63, hv.Source.Position);
-        Assert.Equal (2, hv.Position);
+        Assert.Equal (MEM_STRING_LENGTH, hv.Source.Position);
+        Assert.Equal (2, hv.Address);
         Assert.True (hv.NewKeyDownEvent (Key.CursorLeft));
         Assert.True (hv.NewKeyDownEvent (Key.CursorLeft));
-        Assert.Equal (63, hv.Source.Position);
-        Assert.Equal (1, hv.Position);
+        Assert.Equal (MEM_STRING_LENGTH, hv.Source.Position);
+        Assert.Equal (1, hv.Address);
 
 
         // last position is equal to the source length
         // last position is equal to the source length
         Assert.True (hv.NewKeyDownEvent (Key.End));
         Assert.True (hv.NewKeyDownEvent (Key.End));
-        Assert.Equal (63, hv.Source.Position);
-        Assert.Equal (64, hv.Position);
-        Assert.Equal (hv.Position - 1, hv.Source.Length);
+        Assert.Equal (MEM_STRING_LENGTH, hv.Source.Position);
+        Assert.Equal (64, hv.Address);
+        Assert.Equal (hv.Address - 1, hv.Source.Length);
     }
     }
 
 
     [Fact]
     [Fact]
     public void Position_Using_Encoding_Unicode ()
     public void Position_Using_Encoding_Unicode ()
     {
     {
-        var hv = new HexView (LoadStream (true)) { Width = 20, Height = 20 };
+        var hv = new HexView (LoadStream (null, out _, true)) { Width = 20, Height = 20 };
 
 
         // Needed because HexView relies on LayoutComplete to calc sizes
         // Needed because HexView relies on LayoutComplete to calc sizes
         hv.LayoutSubviews ();
         hv.LayoutSubviews ();
         Assert.Equal (126, hv.Source.Length);
         Assert.Equal (126, hv.Source.Length);
         Assert.Equal (126, hv.Source.Position);
         Assert.Equal (126, hv.Source.Position);
-        Assert.Equal (1, hv.Position);
+        Assert.Equal (1, hv.Address);
 
 
         // left side needed to press twice
         // left side needed to press twice
         Assert.True (hv.NewKeyDownEvent (Key.CursorRight));
         Assert.True (hv.NewKeyDownEvent (Key.CursorRight));
         Assert.Equal (126, hv.Source.Position);
         Assert.Equal (126, hv.Source.Position);
-        Assert.Equal (1, hv.Position);
+        Assert.Equal (1, hv.Address);
         Assert.True (hv.NewKeyDownEvent (Key.CursorRight));
         Assert.True (hv.NewKeyDownEvent (Key.CursorRight));
         Assert.Equal (126, hv.Source.Position);
         Assert.Equal (126, hv.Source.Position);
-        Assert.Equal (2, hv.Position);
+        Assert.Equal (2, hv.Address);
 
 
         // right side only needed to press one time
         // right side only needed to press one time
         Assert.True (hv.NewKeyDownEvent (Key.Tab));
         Assert.True (hv.NewKeyDownEvent (Key.Tab));
         Assert.Equal (126, hv.Source.Position);
         Assert.Equal (126, hv.Source.Position);
-        Assert.Equal (2, hv.Position);
+        Assert.Equal (2, hv.Address);
         Assert.True (hv.NewKeyDownEvent (Key.CursorLeft));
         Assert.True (hv.NewKeyDownEvent (Key.CursorLeft));
         Assert.Equal (126, hv.Source.Position);
         Assert.Equal (126, hv.Source.Position);
-        Assert.Equal (1, hv.Position);
+        Assert.Equal (1, hv.Address);
 
 
         // last position is equal to the source length
         // last position is equal to the source length
         Assert.True (hv.NewKeyDownEvent (Key.End));
         Assert.True (hv.NewKeyDownEvent (Key.End));
         Assert.Equal (126, hv.Source.Position);
         Assert.Equal (126, hv.Source.Position);
-        Assert.Equal (127, hv.Position);
-        Assert.Equal (hv.Position - 1, hv.Source.Length);
+        Assert.Equal (127, hv.Address);
+        Assert.Equal (hv.Address - 1, hv.Source.Length);
     }
     }
 
 
     [Fact]
     [Fact]
-    [AutoInitShutdown]
     public void PositionChanged_Event ()
     public void PositionChanged_Event ()
     {
     {
-        var hv = new HexView (LoadStream ()) { Width = Dim.Fill (), Height = Dim.Fill () };
+        var hv = new HexView (LoadStream (null, out _)) { Width = 20, Height = 10 };
+        Application.Top = new Toplevel ();
+        Application.Top.Add (hv);
+
+        Application.Top.LayoutSubviews ();
+
         HexViewEventArgs hexViewEventArgs = null;
         HexViewEventArgs hexViewEventArgs = null;
         hv.PositionChanged += (s, e) => hexViewEventArgs = e;
         hv.PositionChanged += (s, e) => hexViewEventArgs = e;
-        var top = new Toplevel ();
-        top.Add (hv);
-        Application.Begin (top);
+
+        Assert.Equal (12, hv.BytesPerLine);
 
 
         Assert.True (hv.NewKeyDownEvent (Key.CursorRight)); // left side must press twice
         Assert.True (hv.NewKeyDownEvent (Key.CursorRight)); // left side must press twice
         Assert.True (hv.NewKeyDownEvent (Key.CursorRight));
         Assert.True (hv.NewKeyDownEvent (Key.CursorRight));
@@ -364,87 +398,93 @@ public class HexViewTests
 
 
         Assert.Equal (12, hexViewEventArgs.BytesPerLine);
         Assert.Equal (12, hexViewEventArgs.BytesPerLine);
         Assert.Equal (new (2, 2), hexViewEventArgs.CursorPosition);
         Assert.Equal (new (2, 2), hexViewEventArgs.CursorPosition);
-        Assert.Equal (14, hexViewEventArgs.Position);
-        top.Dispose ();
+        Assert.Equal (14, hexViewEventArgs.Address);
+        Application.Top.Dispose ();
+        Application.ResetState (true);
     }
     }
 
 
     [Fact]
     [Fact]
-    [AutoInitShutdown]
     public void Source_Sets_DisplayStart_And_Position_To_Zero_If_Greater_Than_Source_Length ()
     public void Source_Sets_DisplayStart_And_Position_To_Zero_If_Greater_Than_Source_Length ()
     {
     {
-        var hv = new HexView (LoadStream ()) { Width = 10, Height = 5 };
-        var top = new Toplevel ();
-        top.Add (hv);
-        Application.Begin (top);
+        var hv = new HexView (LoadStream (null, out _)) { Width = 10, Height = 5 };
+        Application.Top = new Toplevel ();
+        Application.Top.Add (hv);
+
+        hv.LayoutSubviews ();
 
 
         Assert.True (hv.NewKeyDownEvent (Key.End));
         Assert.True (hv.NewKeyDownEvent (Key.End));
         Assert.Equal (62, hv.DisplayStart);
         Assert.Equal (62, hv.DisplayStart);
-        Assert.Equal (64, hv.Position);
+        Assert.Equal (64, hv.Address);
 
 
         hv.Source = new MemoryStream ();
         hv.Source = new MemoryStream ();
         Assert.Equal (0, hv.DisplayStart);
         Assert.Equal (0, hv.DisplayStart);
-        Assert.Equal (0, hv.Position - 1);
+        Assert.Equal (0, hv.Address - 1);
 
 
-        hv.Source = LoadStream ();
+        hv.Source = LoadStream (null, out _);
         hv.Width = Dim.Fill ();
         hv.Width = Dim.Fill ();
         hv.Height = Dim.Fill ();
         hv.Height = Dim.Fill ();
-        top.LayoutSubviews ();
+        Application.Top.LayoutSubviews ();
         Assert.Equal (0, hv.DisplayStart);
         Assert.Equal (0, hv.DisplayStart);
-        Assert.Equal (0, hv.Position - 1);
+        Assert.Equal (0, hv.Address - 1);
 
 
         Assert.True (hv.NewKeyDownEvent (Key.End));
         Assert.True (hv.NewKeyDownEvent (Key.End));
         Assert.Equal (0, hv.DisplayStart);
         Assert.Equal (0, hv.DisplayStart);
-        Assert.Equal (64, hv.Position);
+        Assert.Equal (64, hv.Address);
 
 
         hv.Source = new MemoryStream ();
         hv.Source = new MemoryStream ();
         Assert.Equal (0, hv.DisplayStart);
         Assert.Equal (0, hv.DisplayStart);
-        Assert.Equal (0, hv.Position - 1);
-        top.Dispose ();
+        Assert.Equal (0, hv.Address - 1);
+        Application.Top.Dispose ();
+        Application.ResetState (true);
     }
     }
 
 
-    private Stream LoadStream (bool unicode = false)
+    private const string MEM_STRING = "Hello world.\nThis is a test of the Emergency Broadcast System.\n";
+    private const int MEM_STRING_LENGTH = 63;
+
+    private Stream LoadStream (string? memString, out long numBytesInMemString, bool unicode = false)
     {
     {
         var stream = new MemoryStream ();
         var stream = new MemoryStream ();
         byte [] bArray;
         byte [] bArray;
-        var memString = "Hello world.\nThis is a test of the Emergency Broadcast System.\n";
 
 
-        Assert.Equal (63, memString.Length);
+        Assert.Equal (MEM_STRING_LENGTH, MEM_STRING.Length);
+
+        if (memString is null)
+        {
+            memString = MEM_STRING;
+        }
 
 
         if (unicode)
         if (unicode)
         {
         {
             bArray = Encoding.Unicode.GetBytes (memString);
             bArray = Encoding.Unicode.GetBytes (memString);
-            Assert.Equal (126, bArray.Length);
         }
         }
         else
         else
         {
         {
             bArray = Encoding.Default.GetBytes (memString);
             bArray = Encoding.Default.GetBytes (memString);
-            Assert.Equal (63, bArray.Length);
         }
         }
+        numBytesInMemString = bArray.Length;
 
 
         stream.Write (bArray);
         stream.Write (bArray);
 
 
         return stream;
         return stream;
     }
     }
 
 
-    private class NonSeekableStream : Stream
+    private class NonSeekableStream (Stream baseStream) : Stream
     {
     {
-        private readonly Stream m_stream;
-        public NonSeekableStream (Stream baseStream) { m_stream = baseStream; }
-        public override bool CanRead => m_stream.CanRead;
+        public override bool CanRead => baseStream.CanRead;
         public override bool CanSeek => false;
         public override bool CanSeek => false;
-        public override bool CanWrite => m_stream.CanWrite;
+        public override bool CanWrite => baseStream.CanWrite;
         public override long Length => throw new NotSupportedException ();
         public override long Length => throw new NotSupportedException ();
 
 
         public override long Position
         public override long Position
         {
         {
-            get => m_stream.Position;
+            get => baseStream.Position;
             set => throw new NotSupportedException ();
             set => throw new NotSupportedException ();
         }
         }
 
 
-        public override void Flush () { m_stream.Flush (); }
-        public override int Read (byte [] buffer, int offset, int count) { return m_stream.Read (buffer, offset, count); }
+        public override void Flush () { baseStream.Flush (); }
+        public override int Read (byte [] buffer, int offset, int count) { return baseStream.Read (buffer, offset, count); }
         public override long Seek (long offset, SeekOrigin origin) { throw new NotImplementedException (); }
         public override long Seek (long offset, SeekOrigin origin) { throw new NotImplementedException (); }
         public override void SetLength (long value) { throw new NotSupportedException (); }
         public override void SetLength (long value) { throw new NotSupportedException (); }
-        public override void Write (byte [] buffer, int offset, int count) { m_stream.Write (buffer, offset, count); }
+        public override void Write (byte [] buffer, int offset, int count) { baseStream.Write (buffer, offset, count); }
     }
     }
 }
 }

+ 2 - 1
UnitTests/Views/ScrollViewTests.cs

@@ -874,7 +874,8 @@ public class ScrollViewTests (ITestOutputHelper output)
             X = 3,
             X = 3,
             Y = 3,
             Y = 3,
             Width = 10,
             Width = 10,
-            Height = 10
+            Height = 10,
+            TabStop = TabBehavior.TabStop
         };
         };
         sv.SetContentSize (new (50, 50));
         sv.SetContentSize (new (50, 50));