Browse Source

Reorganized View source files to get my head straight

Tig 1 year ago
parent
commit
d874f56282

+ 1 - 29
Terminal.Gui/Application/Application.Keyboard.cs

@@ -267,34 +267,6 @@ public static partial class Application // Keyboard handling
         CommandImplementations [command] = ctx => f ();
     }
 
-    ///// <summary>
-    /////     The <see cref="KeyBindingScope.Application"/> key bindings.
-    ///// </summary>
-    //private static readonly Dictionary<Key, List<View?>> _keyBindings = new ();
-
-    ///// <summary>
-    ///// Gets the list of <see cref="KeyBindingScope.Application"/> key bindings.
-    ///// </summary>
-    //public static Dictionary<Key, List<View?>> GetKeyBindings () { return _keyBindings; }
-
-    ///// <summary>
-    /////     Adds an  <see cref="KeyBindingScope.Application"/> scoped key binding.
-    ///// </summary>
-    ///// <remarks>
-    /////     This is an internal method used by the <see cref="View"/> class to add Application key bindings.
-    ///// </remarks>
-    ///// <param name="key">The key being bound.</param>
-    ///// <param name="view">The view that is bound to the key. If <see langword="null"/>, <see cref="Application.Current"/> will be used.</param>
-    //internal static void AddKeyBinding (Key key, View? view)
-    //{
-    //    if (!_keyBindings.ContainsKey (key))
-    //    {
-    //        _keyBindings [key] = [];
-    //    }
-
-    //    _keyBindings [key].Add (view);
-    //}
-
     internal static void AddApplicationKeyBindings ()
     {
         // Things this view knows how to do
@@ -326,7 +298,7 @@ public static partial class Application // Keyboard handling
                    );
 
         AddCommand (
-                    Command.NextView,    
+                    Command.NextView,
                     () =>
                     {
                         // TODO: Move this method to Application.Navigation.cs

+ 0 - 42
Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs

@@ -603,48 +603,6 @@ public abstract class ConsoleDriver
     #endregion
 }
 
-/// <summary>Terminal Cursor Visibility settings.</summary>
-/// <remarks>
-///     Hex value are set as 0xAABBCCDD where : AA stand for the TERMINFO DECSUSR parameter value to be used under
-///     Linux and MacOS BB stand for the NCurses curs_set parameter value to be used under Linux and MacOS CC stand for the
-///     CONSOLE_CURSOR_INFO.bVisible parameter value to be used under Windows DD stand for the CONSOLE_CURSOR_INFO.dwSize
-///     parameter value to be used under Windows
-/// </remarks>
-public enum CursorVisibility
-{
-    /// <summary>Cursor caret has default</summary>
-    /// <remarks>
-    ///     Works under Xterm-like terminal otherwise this is equivalent to <see ref="Underscore"/>. This default directly
-    ///     depends on the XTerm user configuration settings, so it could be Block, I-Beam, Underline with possible blinking.
-    /// </remarks>
-    Default = 0x00010119,
-
-    /// <summary>Cursor caret is hidden</summary>
-    Invisible = 0x03000019,
-
-    /// <summary>Cursor caret is normally shown as a blinking underline bar _</summary>
-    Underline = 0x03010119,
-
-    /// <summary>Cursor caret is normally shown as a underline bar _</summary>
-    /// <remarks>Under Windows, this is equivalent to <see ref="UnderscoreBlinking"/></remarks>
-    UnderlineFix = 0x04010119,
-
-    /// <summary>Cursor caret is displayed a blinking vertical bar |</summary>
-    /// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Underscore"/></remarks>
-    Vertical = 0x05010119,
-
-    /// <summary>Cursor caret is displayed a blinking vertical bar |</summary>
-    /// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Underscore"/></remarks>
-    VerticalFix = 0x06010119,
-
-    /// <summary>Cursor caret is displayed as a blinking block ▉</summary>
-    Box = 0x01020164,
-
-    /// <summary>Cursor caret is displayed a block ▉</summary>
-    /// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Block"/></remarks>
-    BoxFix = 0x02020164
-}
-
 /// <summary>
 ///     The <see cref="KeyCode"/> enumeration encodes key information from <see cref="ConsoleDriver"/>s and provides a
 ///     consistent way for application code to specify keys and receive key events.

+ 44 - 0
Terminal.Gui/ConsoleDrivers/CursorVisibility.cs

@@ -0,0 +1,44 @@
+#nullable enable
+namespace Terminal.Gui;
+
+/// <summary>Terminal Cursor Visibility settings.</summary>
+/// <remarks>
+///     Hex value are set as 0xAABBCCDD where : AA stand for the TERMINFO DECSUSR parameter value to be used under
+///     Linux and MacOS BB stand for the NCurses curs_set parameter value to be used under Linux and MacOS CC stand for the
+///     CONSOLE_CURSOR_INFO.bVisible parameter value to be used under Windows DD stand for the CONSOLE_CURSOR_INFO.dwSize
+///     parameter value to be used under Windows
+/// </remarks>
+public enum CursorVisibility
+{
+    /// <summary>Cursor caret has default</summary>
+    /// <remarks>
+    ///     Works under Xterm-like terminal otherwise this is equivalent to <see ref="Underscore"/>. This default directly
+    ///     depends on the XTerm user configuration settings, so it could be Block, I-Beam, Underline with possible blinking.
+    /// </remarks>
+    Default = 0x00010119,
+
+    /// <summary>Cursor caret is hidden</summary>
+    Invisible = 0x03000019,
+
+    /// <summary>Cursor caret is normally shown as a blinking underline bar _</summary>
+    Underline = 0x03010119,
+
+    /// <summary>Cursor caret is normally shown as a underline bar _</summary>
+    /// <remarks>Under Windows, this is equivalent to <see ref="UnderscoreBlinking"/></remarks>
+    UnderlineFix = 0x04010119,
+
+    /// <summary>Cursor caret is displayed a blinking vertical bar |</summary>
+    /// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Underscore"/></remarks>
+    Vertical = 0x05010119,
+
+    /// <summary>Cursor caret is displayed a blinking vertical bar |</summary>
+    /// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Underscore"/></remarks>
+    VerticalFix = 0x06010119,
+
+    /// <summary>Cursor caret is displayed as a blinking block ▉</summary>
+    Box = 0x01020164,
+
+    /// <summary>Cursor caret is displayed a block ▉</summary>
+    /// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Block"/></remarks>
+    BoxFix = 0x02020164
+}

+ 29 - 0
Terminal.Gui/View/DrawEventArgs.cs

@@ -0,0 +1,29 @@
+namespace Terminal.Gui;
+
+/// <summary>Event args for draw events</summary>
+public class DrawEventArgs : EventArgs
+{
+    /// <summary>Creates a new instance of the <see cref="DrawEventArgs"/> class.</summary>
+    /// <param name="newViewport">
+    ///     The Content-relative rectangle describing the new visible viewport into the
+    ///     <see cref="View"/>.
+    /// </param>
+    /// <param name="oldViewport">
+    ///     The Content-relative rectangle describing the old visible viewport into the
+    ///     <see cref="View"/>.
+    /// </param>
+    public DrawEventArgs (Rectangle newViewport, Rectangle oldViewport)
+    {
+        NewViewport = newViewport;
+        OldViewport = oldViewport;
+    }
+
+    /// <summary>If set to true, the draw operation will be canceled, if applicable.</summary>
+    public bool Cancel { get; set; }
+
+    /// <summary>Gets the Content-relative rectangle describing the old visible viewport into the <see cref="View"/>.</summary>
+    public Rectangle OldViewport { get; }
+
+    /// <summary>Gets the Content-relative rectangle describing the currently visible viewport into the <see cref="View"/>.</summary>
+    public Rectangle NewViewport { get; }
+}

+ 12 - 0
Terminal.Gui/View/Layout/LayoutEventArgs.cs

@@ -0,0 +1,12 @@
+namespace Terminal.Gui;
+
+/// <summary>Event arguments for the <see cref="View.LayoutComplete"/> event.</summary>
+public class LayoutEventArgs : EventArgs
+{
+    /// <summary>Creates a new instance of the <see cref="Terminal.Gui.LayoutEventArgs"/> class.</summary>
+    /// <param name="oldContentSize">The view that the event is about.</param>
+    public LayoutEventArgs (Size oldContentSize) { OldContentSize = oldContentSize; }
+
+    /// <summary>The viewport of the <see cref="View"/> before it was laid out.</summary>
+    public Size OldContentSize { get; set; }
+}

+ 27 - 0
Terminal.Gui/View/Navigation/FocusEventArgs.cs

@@ -0,0 +1,27 @@
+namespace Terminal.Gui;
+
+/// <summary>Defines the event arguments for <see cref="View.SetFocus()"/></summary>
+public class FocusEventArgs : EventArgs
+{
+    /// <summary>Constructs.</summary>
+    /// <param name="leaving">The view that is losing focus.</param>
+    /// <param name="entering">The view that is gaining focus.</param>
+    public FocusEventArgs (View leaving, View entering) {
+        Leaving = leaving;
+        Entering = entering;
+    }
+
+    /// <summary>
+    ///     Indicates if the current focus event has already been processed and the driver should stop notifying any other
+    ///     event subscriber. It's important to set this value to true specially when updating any View's layout from inside the
+    ///     subscriber method.
+    /// </summary>
+    public bool Handled { get; set; }
+
+    /// <summary>Indicates the view that is losing focus.</summary>
+    public View Leaving { get; set; }
+
+    /// <summary>Indicates the view that is gaining focus.</summary>
+    public View Entering { get; set; }
+
+}

+ 1 - 1
Terminal.Gui/View/ViewAdornments.cs → Terminal.Gui/View/View.Adornments.cs

@@ -3,7 +3,7 @@ using System.Text.Json.Serialization;
 
 namespace Terminal.Gui;
 
-public partial class View
+public partial class View // Adornments
 {
     /// <summary>
     ///    Initializes the Adornments of the View. Called by the constructor.

+ 15 - 0
Terminal.Gui/View/View.Arrangement.cs

@@ -0,0 +1,15 @@
+namespace Terminal.Gui;
+
+public partial class View
+{
+    /// <summary>
+    ///    Gets or sets the user actions that are enabled for the view within it's <see cref="SuperView"/>.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    ///     Sizing or moving a view is only possible if the <see cref="View"/> is part of a <see cref="SuperView"/> and
+    ///     the relevant position and dimensions of the <see cref="View"/> are independent of other SubViews
+    /// </para>
+    /// </remarks>
+    public ViewArrangement Arrangement { get; set; }
+}

+ 0 - 0
Terminal.Gui/View/ViewContent.cs → Terminal.Gui/View/View.Content.cs


+ 35 - 0
Terminal.Gui/View/View.Cursor.cs

@@ -0,0 +1,35 @@
+namespace Terminal.Gui;
+
+public partial class View
+{
+    /// <summary>
+    ///     Gets or sets the cursor style to be used when the view is focused. The default is
+    ///     <see cref="CursorVisibility.Invisible"/>.
+    /// </summary>
+    public CursorVisibility CursorVisibility { get; set; } = CursorVisibility.Invisible;
+
+    /// <summary>
+    ///     Positions the cursor in the right position based on the currently focused view in the chain.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Views that are focusable should override <see cref="PositionCursor()"/> to make sure that the cursor is
+    ///         placed in a location that makes sense. Some terminals do not have a way of hiding the cursor, so it can be
+    ///         distracting to have the cursor left at the last focused view. So views should make sure that they place the
+    ///         cursor in a visually sensible place. The default implementation of <see cref="PositionCursor()"/> will place the
+    ///         cursor at either the hotkey (if defined) or <c>0,0</c>.
+    ///     </para>
+    /// </remarks>
+    /// <returns>Viewport-relative cursor position. Return <see langword="null"/> to ensure the cursor is not visible.</returns>
+    public virtual Point? PositionCursor ()
+    {
+        if (IsInitialized && CanFocus && HasFocus)
+        {
+            // By default, position the cursor at the hotkey (if any) or 0, 0.
+            Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0);
+        }
+
+        // Returning null will hide the cursor.
+        return null;
+    }
+}

+ 0 - 0
Terminal.Gui/View/ViewDiagnostics.cs → Terminal.Gui/View/View.Diagnostics.cs


+ 1 - 1
Terminal.Gui/View/ViewDrawing.cs → Terminal.Gui/View/View.Drawing.cs

@@ -2,7 +2,7 @@
 
 namespace Terminal.Gui;
 
-public partial class View
+public partial class View // Drawing APIs
 {
     private ColorScheme _colorScheme;
 

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

@@ -0,0 +1,320 @@
+namespace Terminal.Gui;
+
+public partial class View // SuperView/SubView hierarchy management (SuperView, SubViews, Add, Remove, etc.)
+{
+    private static readonly IList<View> _empty = new List<View> (0).AsReadOnly ();
+    internal bool _addingView;
+    private List<View> _subviews; // This is null, and allocated on demand.
+    private View _superView;
+
+    /// <summary>Indicates whether the view was added to <see cref="SuperView"/>.</summary>
+    public bool IsAdded { get; private set; }
+
+    /// <summary>This returns a list of the subviews contained by this view.</summary>
+    /// <value>The subviews.</value>
+    public IList<View> Subviews => _subviews?.AsReadOnly () ?? _empty;
+
+    /// <summary>Returns the container for this view, or null if this view has not been added to a container.</summary>
+    /// <value>The super view.</value>
+    public virtual View SuperView
+    {
+        get => _superView;
+        set => throw new NotImplementedException ();
+    }
+
+    // Internally, we use InternalSubviews rather than subviews, as we do not expect us
+    // to make the same mistakes our users make when they poke at the Subviews.
+    internal IList<View> InternalSubviews => _subviews ?? _empty;
+
+    /// <summary>Adds a subview (child) to this view.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         The Views that have been added to this view can be retrieved via the <see cref="Subviews"/> property. See also
+    ///         <seealso cref="Remove(View)"/> <seealso cref="RemoveAll"/>
+    ///     </para>
+    ///     <para>
+    ///         Subviews will be disposed when this View is disposed. In other-words, calling this method causes
+    ///         the lifecycle of the subviews to be transferred to this View.
+    ///     </para>
+    /// </remarks>
+    /// <param name="view">The view to add.</param>
+    /// <returns>The view that was added.</returns>
+    public virtual View Add (View view)
+    {
+        if (view is null)
+        {
+            return view;
+        }
+
+        if (_subviews is null)
+        {
+            _subviews = new ();
+        }
+
+        if (_tabIndexes is null)
+        {
+            _tabIndexes = new ();
+        }
+
+        _subviews.Add (view);
+        _tabIndexes.Add (view);
+        view._superView = this;
+
+        if (view.CanFocus)
+        {
+            _addingView = true;
+
+            if (SuperView?.CanFocus == false)
+            {
+                SuperView._addingView = true;
+                SuperView.CanFocus = true;
+                SuperView._addingView = false;
+            }
+
+            // QUESTION: This automatic behavior of setting CanFocus to true on the SuperView is not documented, and is annoying.
+            CanFocus = true;
+            view._tabIndex = _tabIndexes.IndexOf (view);
+            _addingView = false;
+        }
+
+        if (view.Enabled && !Enabled)
+        {
+            view._oldEnabled = true;
+            view.Enabled = false;
+        }
+
+        OnAdded (new (this, view));
+
+        if (IsInitialized && !view.IsInitialized)
+        {
+            view.BeginInit ();
+            view.EndInit ();
+        }
+
+        CheckDimAuto ();
+        SetNeedsLayout ();
+        SetNeedsDisplay ();
+
+        return view;
+    }
+
+    /// <summary>Adds the specified views (children) to the view.</summary>
+    /// <param name="views">Array of one or more views (can be optional parameter).</param>
+    /// <remarks>
+    ///     <para>
+    ///         The Views that have been added to this view can be retrieved via the <see cref="Subviews"/> property. See also
+    ///         <seealso cref="Remove(View)"/> and <seealso cref="RemoveAll"/>.
+    ///     </para>
+    ///     <para>
+    ///         Subviews will be disposed when this View is disposed. In other-words, calling this method causes
+    ///         the lifecycle of the subviews to be transferred to this View.
+    ///     </para>
+    /// </remarks>
+    public void Add (params View [] views)
+    {
+        if (views is null)
+        {
+            return;
+        }
+
+        foreach (View view in views)
+        {
+            Add (view);
+        }
+    }
+
+    /// <summary>Event fired when this view is added to another.</summary>
+    public event EventHandler<SuperViewChangedEventArgs> Added;
+
+    /// <summary>Get the top superview of a given <see cref="View"/>.</summary>
+    /// <returns>The superview view.</returns>
+    public View GetTopSuperView (View view = null, View superview = null)
+    {
+        View top = superview ?? Application.Top;
+
+        for (View v = view?.SuperView ?? this?.SuperView; v != null; v = v.SuperView)
+        {
+            top = v;
+
+            if (top == superview)
+            {
+                break;
+            }
+        }
+
+        return top;
+    }
+
+    /// <summary>Method invoked when a subview is being added to this view.</summary>
+    /// <param name="e">Event where <see cref="ViewEventArgs.View"/> is the subview being added.</param>
+    public virtual void OnAdded (SuperViewChangedEventArgs e)
+    {
+        View view = e.Child;
+        view.IsAdded = true;
+        view.OnResizeNeeded ();
+        view.Added?.Invoke (this, e);
+    }
+
+    /// <summary>Method invoked when a subview is being removed from this view.</summary>
+    /// <param name="e">Event args describing the subview being removed.</param>
+    public virtual void OnRemoved (SuperViewChangedEventArgs e)
+    {
+        View view = e.Child;
+        view.IsAdded = false;
+        view.Removed?.Invoke (this, e);
+    }
+
+    /// <summary>Removes a subview added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         Normally Subviews will be disposed when this View is disposed. Removing a Subview causes ownership of the
+    ///         Subview's
+    ///         lifecycle to be transferred to the caller; the caller muse call <see cref="Dispose"/>.
+    ///     </para>
+    /// </remarks>
+    public virtual View Remove (View view)
+    {
+        if (view is null || _subviews is null)
+        {
+            return view;
+        }
+
+        Rectangle touched = view.Frame;
+        _subviews.Remove (view);
+        _tabIndexes.Remove (view);
+        view._superView = null;
+        view._tabIndex = -1;
+        SetNeedsLayout ();
+        SetNeedsDisplay ();
+
+        foreach (View v in _subviews)
+        {
+            if (v.Frame.IntersectsWith (touched))
+            {
+                view.SetNeedsDisplay ();
+            }
+        }
+
+        OnRemoved (new (this, view));
+
+        if (Focused == view)
+        {
+            Focused = null;
+        }
+
+        return view;
+    }
+
+    /// <summary>
+    ///     Removes all subviews (children) added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Normally Subviews will be disposed when this View is disposed. Removing a Subview causes ownership of the
+    ///         Subview's
+    ///         lifecycle to be transferred to the caller; the caller must call <see cref="Dispose"/> on any Views that were
+    ///         added.
+    ///     </para>
+    /// </remarks>
+    public virtual void RemoveAll ()
+    {
+        if (_subviews is null)
+        {
+            return;
+        }
+
+        while (_subviews.Count > 0)
+        {
+            Remove (_subviews [0]);
+        }
+    }
+
+    /// <summary>Event fired when this view is removed from another.</summary>
+    public event EventHandler<SuperViewChangedEventArgs> Removed;
+
+
+    /// <summary>Moves <paramref name="subview"/> one position towards the start of the <see cref="Subviews"/> list</summary>
+    /// <param name="subview">The subview to move forward.</param>
+    public void BringSubviewForward (View subview)
+    {
+        PerformActionForSubview (
+                                 subview,
+                                 x =>
+                                 {
+                                     int idx = _subviews.IndexOf (x);
+
+                                     if (idx + 1 < _subviews.Count)
+                                     {
+                                         _subviews.Remove (x);
+                                         _subviews.Insert (idx + 1, x);
+                                     }
+                                 }
+                                );
+    }
+
+    /// <summary>Moves <paramref name="subview"/> to the start of the <see cref="Subviews"/> list.</summary>
+    /// <param name="subview">The subview to send to the start.</param>
+    public void BringSubviewToFront (View subview)
+    {
+        PerformActionForSubview (
+                                 subview,
+                                 x =>
+                                 {
+                                     _subviews.Remove (x);
+                                     _subviews.Add (x);
+                                 }
+                                );
+    }
+
+
+    /// <summary>Moves <paramref name="subview"/> one position towards the end of the <see cref="Subviews"/> list</summary>
+    /// <param name="subview">The subview to move backwards.</param>
+    public void SendSubviewBackwards (View subview)
+    {
+        PerformActionForSubview (
+                                 subview,
+                                 x =>
+                                 {
+                                     int idx = _subviews.IndexOf (x);
+
+                                     if (idx > 0)
+                                     {
+                                         _subviews.Remove (x);
+                                         _subviews.Insert (idx - 1, x);
+                                     }
+                                 }
+                                );
+    }
+
+    /// <summary>Moves <paramref name="subview"/> to the end of the <see cref="Subviews"/> list.</summary>
+    /// <param name="subview">The subview to send to the end.</param>
+    public void SendSubviewToBack (View subview)
+    {
+        PerformActionForSubview (
+                                 subview,
+                                 x =>
+                                 {
+                                     _subviews.Remove (x);
+                                     _subviews.Insert (0, subview);
+                                 }
+                                );
+    }
+
+    /// <summary>
+    ///     Internal API that runs <paramref name="action"/> on a subview if it is part of the <see cref="Subviews"/> list.
+    /// </summary>
+    /// <param name="subview"></param>
+    /// <param name="action"></param>
+    private void PerformActionForSubview (View subview, Action<View> action)
+    {
+        if (_subviews.Contains (subview))
+        {
+            action (subview);
+        }
+
+        // BUGBUG: this is odd. Why is this needed?
+        SetNeedsDisplay ();
+        subview.SetNeedsDisplay ();
+    }
+
+}

+ 1 - 114
Terminal.Gui/View/ViewKeyboard.cs → Terminal.Gui/View/View.Keyboard.cs

@@ -3,7 +3,7 @@ using System.Diagnostics;
 
 namespace Terminal.Gui;
 
-public partial class View
+public partial class View  // Keyboard APIs
 {
     /// <summary>
     ///  Helper to configure all things keyboard related for a View. Called from the View constructor.
@@ -254,119 +254,6 @@ public partial class View
 
     #endregion HotKey Support
 
-    #region Tab/Focus Handling
-
-    // This is null, and allocated on demand.
-    private List<View> _tabIndexes;
-
-    /// <summary>Gets a list of the subviews that are <see cref="TabStop"/>s.</summary>
-    /// <value>The tabIndexes.</value>
-    public IList<View> TabIndexes => _tabIndexes?.AsReadOnly () ?? _empty;
-
-    private int _tabIndex = -1;
-    private int _oldTabIndex;
-
-    /// <summary>
-    ///     Indicates the index of the current <see cref="View"/> from the <see cref="TabIndexes"/> list. See also:
-    ///     <seealso cref="TabStop"/>.
-    /// </summary>
-    public int TabIndex
-    {
-        get => _tabIndex;
-        set
-        {
-            if (!CanFocus)
-            {
-                _tabIndex = -1;
-
-                return;
-            }
-
-            if (SuperView?._tabIndexes is null || SuperView?._tabIndexes.Count == 1)
-            {
-                _tabIndex = 0;
-
-                return;
-            }
-
-            if (_tabIndex == value && TabIndexes.IndexOf (this) == value)
-            {
-                return;
-            }
-
-            _tabIndex = value > SuperView._tabIndexes.Count - 1 ? SuperView._tabIndexes.Count - 1 :
-                        value < 0 ? 0 : value;
-            _tabIndex = GetTabIndex (_tabIndex);
-
-            if (SuperView._tabIndexes.IndexOf (this) != _tabIndex)
-            {
-                SuperView._tabIndexes.Remove (this);
-                SuperView._tabIndexes.Insert (_tabIndex, this);
-                SetTabIndex ();
-            }
-        }
-    }
-
-    private int GetTabIndex (int idx)
-    {
-        var i = 0;
-
-        foreach (View v in SuperView._tabIndexes)
-        {
-            if (v._tabIndex == -1 || v == this)
-            {
-                continue;
-            }
-
-            i++;
-        }
-
-        return Math.Min (i, idx);
-    }
-
-    private void SetTabIndex ()
-    {
-        var i = 0;
-
-        foreach (View v in SuperView._tabIndexes)
-        {
-            if (v._tabIndex == -1)
-            {
-                continue;
-            }
-
-            v._tabIndex = i;
-            i++;
-        }
-    }
-
-    private bool _tabStop = true;
-
-    /// <summary>
-    ///     Gets or sets whether the view is a stop-point for keyboard navigation of focus. Will be <see langword="true"/>
-    ///     only if the <see cref="CanFocus"/> is also <see langword="true"/>. Set to <see langword="false"/> to prevent the
-    ///     view from being a stop-point for keyboard navigation.
-    /// </summary>
-    /// <remarks>
-    ///     The default keyboard navigation keys are <c>Key.Tab</c> and <c>Key>Tab.WithShift</c>. These can be changed by
-    ///     modifying the key bindings (see <see cref="KeyBindings.Add(Key, Command[])"/>) of the SuperView.
-    /// </remarks>
-    public bool TabStop
-    {
-        get => _tabStop;
-        set
-        {
-            if (_tabStop == value)
-            {
-                return;
-            }
-
-            _tabStop = CanFocus && value;
-        }
-    }
-
-    #endregion Tab/Focus Handling
-
     #region Low-level Key handling
 
     #region Key Down Event

+ 1 - 1
Terminal.Gui/View/Layout/ViewLayout.cs → Terminal.Gui/View/View.Layout.cs

@@ -3,7 +3,7 @@ using System.Diagnostics;
 
 namespace Terminal.Gui;
 
-public partial class View
+public partial class View // Layout APIs
 {
     #region Frame
 

+ 1 - 1
Terminal.Gui/View/ViewMouse.cs → Terminal.Gui/View/View.Mouse.cs

@@ -2,7 +2,7 @@
 
 namespace Terminal.Gui;
 
-public partial class View
+public partial class View // Mouse APIs
 {
     [CanBeNull]
     private ColorScheme _savedHighlightColorScheme;

+ 813 - 0
Terminal.Gui/View/View.Navigation.cs

@@ -0,0 +1,813 @@
+namespace Terminal.Gui;
+
+public partial class View // Focus and cross-view navigation management (TabStop, TabIndex, etc...)
+{
+    /// <summary>Returns a value indicating if this View is currently on Top (Active)</summary>
+    public bool IsCurrentTop => Application.Current == this;
+
+    // BUGBUG: This API is poorly defined and implemented. It deeply intertwines the view hierarchy with the tab order.
+    /// <summary>Exposed as `internal` for unit tests. Indicates focus navigation direction.</summary>
+    internal enum NavigationDirection
+    {
+        /// <summary>Navigate forward.</summary>
+        Forward,
+
+        /// <summary>Navigate backwards.</summary>
+        Backward
+    }
+
+    /// <summary>Invoked when this view is gaining focus (entering).</summary>
+    /// <param name="leavingView">The view that is leaving focus.</param>
+    /// <returns> <see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
+    /// <remarks>
+    ///     <para>
+    ///         Overrides must call the base class method to ensure that the <see cref="Enter"/> event is raised. If the event
+    ///         is handled, the method should return <see langword="true"/>.
+    ///     </para>
+    /// </remarks>
+    public virtual bool OnEnter (View leavingView)
+    {
+        var args = new FocusEventArgs (leavingView, this);
+        Enter?.Invoke (this, args);
+
+        if (args.Handled)
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+    /// <summary>Invoked when this view is losing focus (leaving).</summary>
+    /// <param name="enteringView">The view that is entering focus.</param>
+    /// <returns> <see langword="true"/>, if the event was handled, <see langword="false"/> otherwise.</returns>
+    /// <remarks>
+    ///     <para>
+    ///         Overrides must call the base class method to ensure that the <see cref="Leave"/> event is raised. If the event
+    ///         is handled, the method should return <see langword="true"/>.
+    ///     </para>
+    /// </remarks>
+    public virtual bool OnLeave (View enteringView)
+    {
+        var args = new FocusEventArgs (this, enteringView);
+        Leave?.Invoke (this, args);
+
+        if (args.Handled)
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+    /// <summary>Raised when the view is gaining (entering) focus. Can be cancelled.</summary>
+    /// <remarks>
+    ///     Raised by the <see cref="OnEnter"/> virtual method.
+    /// </remarks>
+    public event EventHandler<FocusEventArgs> Enter;
+
+    /// <summary>Raised when the view is losing (leaving) focus. Can be cancelled.</summary>
+    /// <remarks>
+    ///     Raised by the <see cref="OnLeave"/> virtual method.
+    /// </remarks>
+    public event EventHandler<FocusEventArgs> Leave;
+
+    private NavigationDirection _focusDirection;
+
+    /// <summary>
+    ///     Gets or sets the focus direction for this view and all subviews.
+    ///     Setting this property will set the focus direction for all views up the SuperView hierarchy.
+    /// </summary>
+    internal NavigationDirection FocusDirection
+    {
+        get => SuperView?.FocusDirection ?? _focusDirection;
+        set
+        {
+            if (SuperView is { })
+            {
+                SuperView.FocusDirection = value;
+            }
+            else
+            {
+                _focusDirection = value;
+            }
+        }
+    }
+
+    private bool _hasFocus;
+
+    /// <summary>
+    ///     Gets or sets whether this view has focus.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Causes the <see cref="OnEnter"/> and <see cref="OnLeave"/> virtual methods (and <see cref="Enter"/> and
+    ///         <see cref="Leave"/> events to be raised) when the value changes.
+    ///     </para>
+    ///     <para>
+    ///         Setting this property to <see langword="false"/> will recursively set <see cref="HasFocus"/> to
+    ///         <see langword="false"/>
+    ///         for any focused subviews.
+    ///     </para>
+    /// </remarks>
+    public bool HasFocus
+    {
+        // Force the specified view to have focus
+        set => SetHasFocus (value, this, true);
+        get => _hasFocus;
+    }
+
+    /// <summary>
+    ///     Internal API that sets <see cref="HasFocus"/>. This method is called by <c>HasFocus_set</c> and other methods that
+    ///     need to set or remove focus from a view.
+    /// </summary>
+    /// <param name="newHasFocus">The new setting for <see cref="HasFocus"/>.</param>
+    /// <param name="view">The view that will be gaining or losing focus.</param>
+    /// <param name="force">
+    ///     <see langword="true"/> to force Enter/Leave on <paramref name="view"/> regardless of whether it
+    ///     already HasFocus or not.
+    /// </param>
+    /// <remarks>
+    ///     If <paramref name="newHasFocus"/> is <see langword="false"/> and there is a focused subview (<see cref="Focused"/>
+    ///     is not <see langword="null"/>),
+    ///     this method will recursively remove focus from any focused subviews of <see cref="Focused"/>.
+    /// </remarks>
+    private void SetHasFocus (bool newHasFocus, View view, bool force = false)
+    {
+        if (HasFocus != newHasFocus || force)
+        {
+            _hasFocus = newHasFocus;
+
+            if (newHasFocus)
+            {
+                OnEnter (view);
+            }
+            else
+            {
+                OnLeave (view);
+            }
+
+            SetNeedsDisplay ();
+        }
+
+        // Remove focus down the chain of subviews if focus is removed
+        if (!newHasFocus && Focused is { })
+        {
+            View f = Focused;
+            f.OnLeave (view);
+            f.SetHasFocus (false, view);
+            Focused = null;
+        }
+    }
+
+    /// <summary>Raised when <see cref="CanFocus"/> has been changed.</summary>
+    /// <remarks>
+    ///     Raised by the <see cref="OnCanFocusChanged"/> virtual method.
+    /// </remarks>
+    public event EventHandler CanFocusChanged;
+
+    /// <summary>Invoked when the <see cref="CanFocus"/> property from a view is changed.</summary>
+    /// <remarks>
+    ///     Raises the <see cref="CanFocusChanged"/> event.
+    /// </remarks>
+    public virtual void OnCanFocusChanged () { CanFocusChanged?.Invoke (this, EventArgs.Empty); }
+
+    private bool _oldCanFocus;
+    private bool _canFocus;
+
+    /// <summary>Gets or sets a value indicating whether this <see cref="View"/> can be focused.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         <see cref="SuperView"/> must also have <see cref="CanFocus"/> set to <see langword="true"/>.
+    ///     </para>
+    /// </remarks>
+    public bool CanFocus
+    {
+        get => _canFocus;
+        set
+        {
+            if (!_addingView && IsInitialized && SuperView?.CanFocus == false && value)
+            {
+                throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!");
+            }
+
+            if (_canFocus == value)
+            {
+                return;
+            }
+
+            _canFocus = value;
+
+            switch (_canFocus)
+            {
+                case false when _tabIndex > -1:
+                    TabIndex = -1;
+
+                    break;
+                case true when SuperView?.CanFocus == false && _addingView:
+                    SuperView.CanFocus = true;
+
+                    break;
+            }
+
+            if (_canFocus && _tabIndex == -1)
+            {
+                TabIndex = SuperView is { } ? SuperView._tabIndexes.IndexOf (this) : -1;
+            }
+
+            TabStop = _canFocus;
+
+            if (!_canFocus && SuperView?.Focused == this)
+            {
+                SuperView.Focused = null;
+            }
+
+            if (!_canFocus && HasFocus)
+            {
+                SetHasFocus (false, this);
+                SuperView?.EnsureFocus ();
+
+                if (SuperView is { Focused: null })
+                {
+                    SuperView.FocusNext ();
+
+                    if (SuperView.Focused is null && Application.Current is { })
+                    {
+                        Application.Current.FocusNext ();
+                    }
+
+                    ApplicationOverlapped.BringOverlappedTopToFront ();
+                }
+            }
+
+            if (_subviews is { } && IsInitialized)
+            {
+                foreach (View view in _subviews)
+                {
+                    if (view.CanFocus != value)
+                    {
+                        if (!value)
+                        {
+                            view._oldCanFocus = view.CanFocus;
+                            view._oldTabIndex = view._tabIndex;
+                            view.CanFocus = false;
+                            view._tabIndex = -1;
+                        }
+                        else
+                        {
+                            if (_addingView)
+                            {
+                                view._addingView = true;
+                            }
+
+                            view.CanFocus = view._oldCanFocus;
+                            view._tabIndex = view._oldTabIndex;
+                            view._addingView = false;
+                        }
+                    }
+                }
+
+                if (this is Toplevel && Application.Current.Focused != this)
+                {
+                    ApplicationOverlapped.BringOverlappedTopToFront ();
+                }
+            }
+
+            OnCanFocusChanged ();
+            SetNeedsDisplay ();
+        }
+    }
+
+    /// <summary>Returns the currently focused Subview inside this view, or <see langword="null"/> if nothing is focused.</summary>
+    /// <value>The currently focused Subview.</value>
+    public View Focused { get; private set; }
+
+    /// <summary>
+    ///     Returns the most focused Subview in the chain of subviews (the leaf view that has the focus), or
+    ///     <see langword="null"/> if nothing is focused.
+    /// </summary>
+    /// <value>The most focused Subview.</value>
+    public View MostFocused
+    {
+        get
+        {
+            if (Focused is null)
+            {
+                return null;
+            }
+
+            View most = Focused.MostFocused;
+
+            if (most is { })
+            {
+                return most;
+            }
+
+            return Focused;
+        }
+    }
+
+    /// <summary>Causes subview specified by <paramref name="view"/> to enter focus.</summary>
+    /// <param name="view">View.</param>
+    private void SetFocus (View view)
+    {
+        if (view is null)
+        {
+            return;
+        }
+
+        //Console.WriteLine ($"Request to focus {view}");
+        if (!view.CanFocus || !view.Visible || !view.Enabled)
+        {
+            return;
+        }
+
+        if (Focused?._hasFocus == true && Focused == view)
+        {
+            return;
+        }
+
+        if ((Focused?._hasFocus == true && Focused?.SuperView == view) || view == this)
+        {
+            if (!view._hasFocus)
+            {
+                view._hasFocus = true;
+            }
+
+            return;
+        }
+
+        // Make sure that this view is a subview
+        View c;
+
+        for (c = view._superView; c != null; c = c._superView)
+        {
+            if (c == this)
+            {
+                break;
+            }
+        }
+
+        if (c is null)
+        {
+            throw new ArgumentException ("the specified view is not part of the hierarchy of this view");
+        }
+
+        if (Focused is { })
+        {
+            Focused.SetHasFocus (false, view);
+        }
+
+        View f = Focused;
+        Focused = view;
+        Focused.SetHasFocus (true, f);
+        Focused.EnsureFocus ();
+
+        // Send focus upwards
+        if (SuperView is { })
+        {
+            SuperView.SetFocus (this);
+        }
+        else
+        {
+            SetFocus (this);
+        }
+    }
+
+    /// <summary>Causes this view to be focused and entire Superview hierarchy to have the focused order updated.</summary>
+    public void SetFocus ()
+    {
+        if (!CanBeVisible (this) || !Enabled)
+        {
+            if (HasFocus)
+            {
+                SetHasFocus (false, this);
+            }
+
+            return;
+        }
+
+        if (SuperView is { })
+        {
+            SuperView.SetFocus (this);
+        }
+        else
+        {
+            SetFocus (this);
+        }
+    }
+
+    /// <summary>
+    ///     If there is no focused subview, calls <see cref="FocusFirst"/> or <see cref="FocusLast"/> based on
+    ///     <see cref="FocusDirection"/>.
+    ///     does nothing.
+    /// </summary>
+    public void EnsureFocus ()
+    {
+        if (Focused is null && _subviews?.Count > 0)
+        {
+            if (FocusDirection == NavigationDirection.Forward)
+            {
+                FocusFirst ();
+            }
+            else
+            {
+                FocusLast ();
+            }
+        }
+    }
+
+    /// <summary>
+    ///     Focuses the last focusable view in <see cref="View.TabIndexes"/> if one exists. If there are no views in
+    ///     <see cref="View.TabIndexes"/> then the focus is set to the view itself.
+    /// </summary>
+    public void FocusFirst (bool overlapped = false)
+    {
+        if (!CanBeVisible (this))
+        {
+            return;
+        }
+
+        if (_tabIndexes is null)
+        {
+            SuperView?.SetFocus (this);
+
+            return;
+        }
+
+        foreach (View view in _tabIndexes.Where (v => !overlapped || v.Arrangement.HasFlag (ViewArrangement.Overlapped)))
+        {
+            if (view.CanFocus && view._tabStop && view.Visible && view.Enabled)
+            {
+                SetFocus (view);
+
+                return;
+            }
+        }
+    }
+
+    /// <summary>
+    ///     Focuses the last focusable view in <see cref="View.TabIndexes"/> if one exists. If there are no views in
+    ///     <see cref="View.TabIndexes"/> then the focus is set to the view itself.
+    /// </summary>
+    public void FocusLast (bool overlapped = false)
+    {
+        if (!CanBeVisible (this))
+        {
+            return;
+        }
+
+        if (_tabIndexes is null)
+        {
+            SuperView?.SetFocus (this);
+
+            return;
+        }
+
+        foreach (View view in _tabIndexes.Where (v => !overlapped || v.Arrangement.HasFlag (ViewArrangement.Overlapped)).Reverse ())
+        {
+            if (view.CanFocus && view._tabStop && view.Visible && view.Enabled)
+            {
+                SetFocus (view);
+
+                return;
+            }
+        }
+    }
+
+    /// <summary>
+    ///     Focuses the previous view in <see cref="View.TabIndexes"/>. If there is no previous view, the focus is set to the
+    ///     view itself.
+    /// </summary>
+    /// <returns><see langword="true"/> if previous was focused, <see langword="false"/> otherwise.</returns>
+    public bool FocusPrev ()
+    {
+        if (!CanBeVisible (this))
+        {
+            return false;
+        }
+
+        FocusDirection = NavigationDirection.Backward;
+
+        if (TabIndexes is null || TabIndexes.Count == 0)
+        {
+            return false;
+        }
+
+        if (Focused is null)
+        {
+            FocusLast ();
+
+            return Focused != null;
+        }
+
+        int focusedIdx = -1;
+
+        for (int i = TabIndexes.Count; i > 0;)
+        {
+            i--;
+            View w = TabIndexes [i];
+
+            if (w.HasFocus)
+            {
+                if (w.FocusPrev ())
+                {
+                    return true;
+                }
+
+                focusedIdx = i;
+
+                continue;
+            }
+
+            if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled)
+            {
+                Focused.SetHasFocus (false, w);
+
+                // If the focused view is overlapped don't focus on the next if it's not overlapped.
+                if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && !w.Arrangement.HasFlag (ViewArrangement.Overlapped))
+                {
+                    FocusLast (true);
+
+                    return true;
+                }
+
+                // If the focused view is not overlapped and the next is, skip it
+                if (!Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && w.Arrangement.HasFlag (ViewArrangement.Overlapped))
+                {
+                    continue;
+                }
+
+                if (w.CanFocus && w._tabStop && w.Visible && w.Enabled)
+                {
+                    w.FocusLast ();
+                }
+
+                SetFocus (w);
+
+                return true;
+            }
+        }
+
+        // There's no prev view in tab indexes.
+        if (Focused is { })
+        {
+            // Leave Focused
+            Focused.SetHasFocus (false, this);
+
+            if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped))
+            {
+                FocusLast (true);
+
+                return true;
+            }
+
+            // Signal to caller no next view was found
+            Focused = null;
+        }
+
+        return false;
+    }
+
+    /// <summary>
+    ///     Focuses the next view in <see cref="View.TabIndexes"/>. If there is no next view, the focus is set to the view
+    ///     itself.
+    /// </summary>
+    /// <returns><see langword="true"/> if next was focused, <see langword="false"/> otherwise.</returns>
+    public bool FocusNext ()
+    {
+        if (!CanBeVisible (this))
+        {
+            return false;
+        }
+
+        FocusDirection = NavigationDirection.Forward;
+
+        if (TabIndexes is null || TabIndexes.Count == 0)
+        {
+            return false;
+        }
+
+        if (Focused is null)
+        {
+            FocusFirst ();
+
+            return Focused != null;
+        }
+
+        int focusedIdx = -1;
+
+        for (var i = 0; i < TabIndexes.Count; i++)
+        {
+            View w = TabIndexes [i];
+
+            if (w.HasFocus)
+            {
+                if (w.FocusNext ())
+                {
+                    return true;
+                }
+
+                focusedIdx = i;
+
+                continue;
+            }
+
+            if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled)
+            {
+                Focused.SetHasFocus (false, w);
+
+                //// If the focused view is overlapped don't focus on the next if it's not overlapped.
+                //if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped)/* && !w.Arrangement.HasFlag (ViewArrangement.Overlapped)*/)
+                //{
+                //    return false;
+                //}
+
+                //// If the focused view is not overlapped and the next is, skip it
+                //if (!Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && w.Arrangement.HasFlag (ViewArrangement.Overlapped))
+                //{
+                //    continue;
+                //}
+
+                if (w.CanFocus && w._tabStop && w.Visible && w.Enabled)
+                {
+                    w.FocusFirst ();
+                }
+
+                SetFocus (w);
+
+                return true;
+            }
+        }
+
+        // There's no next view in tab indexes.
+        if (Focused is { })
+        {
+            // Leave Focused
+            Focused.SetHasFocus (false, this);
+
+            //if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped))
+            //{
+            //    FocusFirst (true);
+            //    return true;
+            //}
+
+            // Signal to caller no next view was found
+            Focused = null;
+        }
+
+        return false;
+    }
+
+    private View GetMostFocused (View view)
+    {
+        if (view is null)
+        {
+            return null;
+        }
+
+        return view.Focused is { } ? GetMostFocused (view.Focused) : view;
+    }
+
+    #region Tab/Focus Handling
+
+    private List<View> _tabIndexes;
+
+    // TODO: This should be a get-only property?
+    // BUGBUG: This returns an AsReadOnly list, but isn't declared as such.
+    /// <summary>Gets a list of the subviews that are a <see cref="TabStop"/>.</summary>
+    /// <value>The tabIndexes.</value>
+    public IList<View> TabIndexes => _tabIndexes?.AsReadOnly () ?? _empty;
+
+    // TODO: Change this to int? and use null to indicate the view is not in the tab order.
+    private int _tabIndex = -1;
+    private int _oldTabIndex;
+
+    /// <summary>
+    ///     Indicates the index of the current <see cref="View"/> from the <see cref="TabIndexes"/> list. See also:
+    ///     <seealso cref="TabStop"/>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         If the value is -1, the view is not part of the tab order.
+    ///     </para>
+    ///     <para>
+    ///         On set, if <see cref="CanFocus"/> is <see langword="false"/>, <see cref="TabIndex"/> will be set to -1.
+    ///     </para>
+    ///     <para>
+    ///         On set, if <see cref="SuperView"/> is <see langword="null"/> or has not TabStops, <see cref="TabIndex"/> will
+    ///         be set to 0.
+    ///     </para>
+    ///     <para>
+    ///         On set, if <see cref="SuperView"/> has only one TabStop, <see cref="TabIndex"/> will be set to 0.
+    ///     </para>
+    /// </remarks>
+    public int TabIndex
+    {
+        get => _tabIndex;
+        set
+        {
+            if (!CanFocus)
+            {
+                // BUGBUG: Property setters should set the property to the value passed in and not have side effects.
+                _tabIndex = -1;
+
+                return;
+            }
+
+            if (SuperView?._tabIndexes is null || SuperView?._tabIndexes.Count == 1)
+            {
+                // BUGBUG: Property setters should set the property to the value passed in and not have side effects.
+                _tabIndex = 0;
+
+                return;
+            }
+
+            if (_tabIndex == value && TabIndexes.IndexOf (this) == value)
+            {
+                return;
+            }
+
+            _tabIndex = value > SuperView!.TabIndexes.Count - 1 ? SuperView._tabIndexes.Count - 1 :
+                        value < 0 ? 0 : value;
+            _tabIndex = GetGreatestTabIndexInSuperView (_tabIndex);
+
+            if (SuperView._tabIndexes.IndexOf (this) != _tabIndex)
+            {
+                // BUGBUG: we have to use _tabIndexes and not TabIndexes because TabIndexes returns is a read-only version of _tabIndexes
+                SuperView._tabIndexes.Remove (this);
+                SuperView._tabIndexes.Insert (_tabIndex, this);
+                ReorderSuperViewTabIndexes ();
+            }
+        }
+    }
+
+    /// <summary>
+    ///     Gets the greatest <see cref="TabIndex"/> of the <see cref="SuperView"/>'s <see cref="TabIndexes"/> that is less
+    ///     than or equal to <paramref name="idx"/>.
+    /// </summary>
+    /// <param name="idx"></param>
+    /// <returns>The minimum of <paramref name="idx"/> and the <see cref="SuperView"/>'s <see cref="TabIndexes"/>.</returns>
+    private int GetGreatestTabIndexInSuperView (int idx)
+    {
+        var i = 0;
+
+        foreach (View superViewTabStop in SuperView._tabIndexes)
+        {
+            if (superViewTabStop._tabIndex == -1 || superViewTabStop == this)
+            {
+                continue;
+            }
+
+            i++;
+        }
+
+        return Math.Min (i, idx);
+    }
+
+    /// <summary>
+    ///     Re-orders the <see cref="TabIndex"/>s of the views in the <see cref="SuperView"/>'s <see cref="TabIndexes"/>.
+    /// </summary>
+    private void ReorderSuperViewTabIndexes ()
+    {
+        var i = 0;
+
+        foreach (View superViewTabStop in SuperView._tabIndexes)
+        {
+            if (superViewTabStop._tabIndex == -1)
+            {
+                continue;
+            }
+
+            superViewTabStop._tabIndex = i;
+            i++;
+        }
+    }
+
+    private bool _tabStop = true;
+
+    /// <summary>
+    ///     Gets or sets whether the view is a stop-point for keyboard navigation of focus. Will be <see langword="true"/>
+    ///     only if <see cref="CanFocus"/> is <see langword="true"/>. Set to <see langword="false"/> to prevent the
+    ///     view from being a stop-point for keyboard navigation.
+    /// </summary>
+    /// <remarks>
+    ///     The default keyboard navigation keys are <c>Key.Tab</c> and <c>Key>Tab.WithShift</c>. These can be changed by
+    ///     modifying the key bindings (see <see cref="KeyBindings.Add(Key, Command[])"/>) of the SuperView.
+    /// </remarks>
+    public bool TabStop
+    {
+        get => _tabStop;
+        set
+        {
+            if (_tabStop == value)
+            {
+                return;
+            }
+
+            _tabStop = CanFocus && value;
+        }
+    }
+
+    #endregion Tab/Focus Handling
+}

+ 1 - 1
Terminal.Gui/View/ViewText.cs → Terminal.Gui/View/View.Text.cs

@@ -2,7 +2,7 @@
 
 namespace Terminal.Gui;
 
-public partial class View
+public partial class View // Text Property APIs
 {
     /// <summary>
     ///    Initializes the Text of the View. Called by the constructor.

+ 10 - 20
Terminal.Gui/View/ViewArrangement.cs

@@ -1,14 +1,16 @@
 namespace Terminal.Gui;
 
 /// <summary>
-///     Describes what user actions are enabled for arranging a <see cref="View"/> within it's <see cref="View.SuperView"/>.
+///     Describes what user actions are enabled for arranging a <see cref="View"/> within it's <see cref="View.SuperView"/>
+///     .
 ///     See <see cref="View.Arrangement"/>.
 /// </summary>
 /// <remarks>
-/// <para>
-///     Sizing or moving a view is only possible if the <see cref="View"/> is part of a <see cref="View.SuperView"/> and
-///     the relevant position and dimensions of the <see cref="View"/> are independent of other SubViews
-/// </para>
+///     <para>
+///         Sizing or moving a view is only possible if the <see cref="View"/> is part of a <see cref="View.SuperView"/>
+///         and
+///         the relevant position and dimensions of the <see cref="View"/> are independent of other SubViews
+///     </para>
 /// </remarks>
 [Flags]
 public enum ViewArrangement
@@ -56,26 +58,14 @@ public enum ViewArrangement
     Resizable = LeftResizable | RightResizable | TopResizable | BottomResizable,
 
     /// <summary>
-    ///    The view overlap other views.
+    ///     The view overlap other views.
     /// </summary>
     /// <remarks>
     ///     <para>
-    ///         When set, Tab and Shift-Tab will be constrained to the subviews of the view (normally, they will navigate to the next/prev view in the next/prev Tabindex).
+    ///         When set, Tab and Shift-Tab will be constrained to the subviews of the view (normally, they will navigate to
+    ///         the next/prev view in the next/prev Tabindex).
     ///         Use Ctrl-Tab (Ctrl-PageDown) / Ctrl-Shift-Tab (Ctrl-PageUp) to move between overlapped views.
     ///     </para>
     /// </remarks>
     Overlapped = 32
 }
-public partial class View
-{
-    /// <summary>
-    ///    Gets or sets the user actions that are enabled for the view within it's <see cref="SuperView"/>.
-    /// </summary>
-    /// <remarks>
-    /// <para>
-    ///     Sizing or moving a view is only possible if the <see cref="View"/> is part of a <see cref="SuperView"/> and
-    ///     the relevant position and dimensions of the <see cref="View"/> are independent of other SubViews
-    /// </para>
-    /// </remarks>
-    public ViewArrangement Arrangement { get; set; }
-}

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

@@ -13,69 +13,4 @@ public class ViewEventArgs : EventArgs
     ///     child then sender may be the parent while <see cref="View"/> is the child being added.
     /// </remarks>
     public View View { get; }
-}
-
-/// <summary>Event arguments for the <see cref="View.LayoutComplete"/> event.</summary>
-public class LayoutEventArgs : EventArgs
-{
-    /// <summary>Creates a new instance of the <see cref="Terminal.Gui.LayoutEventArgs"/> class.</summary>
-    /// <param name="oldContentSize">The view that the event is about.</param>
-    public LayoutEventArgs (Size oldContentSize) { OldContentSize = oldContentSize; }
-
-    /// <summary>The viewport of the <see cref="View"/> before it was laid out.</summary>
-    public Size OldContentSize { get; set; }
-}
-
-/// <summary>Event args for draw events</summary>
-public class DrawEventArgs : EventArgs
-{
-    /// <summary>Creates a new instance of the <see cref="DrawEventArgs"/> class.</summary>
-    /// <param name="newViewport">
-    ///     The Content-relative rectangle describing the new visible viewport into the
-    ///     <see cref="View"/>.
-    /// </param>
-    /// <param name="oldViewport">
-    ///     The Content-relative rectangle describing the old visible viewport into the
-    ///     <see cref="View"/>.
-    /// </param>
-    public DrawEventArgs (Rectangle newViewport, Rectangle oldViewport)
-    {
-        NewViewport = newViewport;
-        OldViewport = oldViewport;
-    }
-
-    /// <summary>If set to true, the draw operation will be canceled, if applicable.</summary>
-    public bool Cancel { get; set; }
-
-    /// <summary>Gets the Content-relative rectangle describing the old visible viewport into the <see cref="View"/>.</summary>
-    public Rectangle OldViewport { get; }
-
-    /// <summary>Gets the Content-relative rectangle describing the currently visible viewport into the <see cref="View"/>.</summary>
-    public Rectangle NewViewport { get; }
-}
-
-/// <summary>Defines the event arguments for <see cref="View.SetFocus()"/></summary>
-public class FocusEventArgs : EventArgs
-{
-    /// <summary>Constructs.</summary>
-    /// <param name="leaving">The view that is losing focus.</param>
-    /// <param name="entering">The view that is gaining focus.</param>
-    public FocusEventArgs (View leaving, View entering) {
-        Leaving = leaving;
-        Entering = entering;
-    }
-
-    /// <summary>
-    ///     Indicates if the current focus event has already been processed and the driver should stop notifying any other
-    ///     event subscriber. It's important to set this value to true specially when updating any View's layout from inside the
-    ///     subscriber method.
-    /// </summary>
-    public bool Handled { get; set; }
-
-    /// <summary>Indicates the view that is losing focus.</summary>
-    public View Leaving { get; set; }
-
-    /// <summary>Indicates the view that is gaining focus.</summary>
-    public View Entering { get; set; }
-
-}
+}

+ 0 - 948
Terminal.Gui/View/ViewSubViews.cs

@@ -1,948 +0,0 @@
-using System.Diagnostics;
-
-namespace Terminal.Gui;
-
-public partial class View
-{
-    private static readonly IList<View> _empty = new List<View> (0).AsReadOnly ();
-    internal bool _addingView;
-    private List<View> _subviews; // This is null, and allocated on demand.
-    private View _superView;
-
-    /// <summary>Indicates whether the view was added to <see cref="SuperView"/>.</summary>
-    public bool IsAdded { get; private set; }
-
-    /// <summary>Returns a value indicating if this View is currently on Top (Active)</summary>
-    public bool IsCurrentTop => Application.Current == this;
-
-    /// <summary>This returns a list of the subviews contained by this view.</summary>
-    /// <value>The subviews.</value>
-    public IList<View> Subviews => _subviews?.AsReadOnly () ?? _empty;
-
-    /// <summary>Returns the container for this view, or null if this view has not been added to a container.</summary>
-    /// <value>The super view.</value>
-    public virtual View SuperView
-    {
-        get => _superView;
-        set => throw new NotImplementedException ();
-    }
-
-    // Internally, we use InternalSubviews rather than subviews, as we do not expect us
-    // to make the same mistakes our users make when they poke at the Subviews.
-    internal IList<View> InternalSubviews => _subviews ?? _empty;
-
-    /// <summary>Adds a subview (child) to this view.</summary>
-    /// <remarks>
-    ///     <para>
-    ///         The Views that have been added to this view can be retrieved via the <see cref="Subviews"/> property. See also
-    ///         <seealso cref="Remove(View)"/> <seealso cref="RemoveAll"/>
-    ///     </para>
-    ///     <para>
-    ///         Subviews will be disposed when this View is disposed. In other-words, calling this method causes
-    ///         the lifecycle of the subviews to be transferred to this View.
-    ///     </para>
-    /// </remarks>
-    /// <param name="view">The view to add.</param>
-    /// <returns>The view that was added.</returns>
-    public virtual View Add (View view)
-    {
-        if (view is null)
-        {
-            return view;
-        }
-
-        if (_subviews is null)
-        {
-            _subviews = new ();
-        }
-
-        if (_tabIndexes is null)
-        {
-            _tabIndexes = new ();
-        }
-
-        _subviews.Add (view);
-        _tabIndexes.Add (view);
-        view._superView = this;
-
-        if (view.CanFocus)
-        {
-            _addingView = true;
-
-            if (SuperView?.CanFocus == false)
-            {
-                SuperView._addingView = true;
-                SuperView.CanFocus = true;
-                SuperView._addingView = false;
-            }
-
-            // QUESTION: This automatic behavior of setting CanFocus to true on the SuperView is not documented, and is annoying.
-            CanFocus = true;
-            view._tabIndex = _tabIndexes.IndexOf (view);
-            _addingView = false;
-        }
-
-        if (view.Enabled && !Enabled)
-        {
-            view._oldEnabled = true;
-            view.Enabled = false;
-        }
-
-        OnAdded (new (this, view));
-
-        if (IsInitialized && !view.IsInitialized)
-        {
-            view.BeginInit ();
-            view.EndInit ();
-        }
-
-        CheckDimAuto ();
-        SetNeedsLayout ();
-        SetNeedsDisplay ();
-
-        return view;
-    }
-
-    /// <summary>Adds the specified views (children) to the view.</summary>
-    /// <param name="views">Array of one or more views (can be optional parameter).</param>
-    /// <remarks>
-    ///     <para>
-    ///         The Views that have been added to this view can be retrieved via the <see cref="Subviews"/> property. See also
-    ///         <seealso cref="Remove(View)"/> and <seealso cref="RemoveAll"/>.
-    ///     </para>
-    ///     <para>
-    ///         Subviews will be disposed when this View is disposed. In other-words, calling this method causes
-    ///         the lifecycle of the subviews to be transferred to this View.
-    ///     </para>
-    /// </remarks>
-    public void Add (params View [] views)
-    {
-        if (views is null)
-        {
-            return;
-        }
-
-        foreach (View view in views)
-        {
-            Add (view);
-        }
-    }
-
-    /// <summary>Event fired when this view is added to another.</summary>
-    public event EventHandler<SuperViewChangedEventArgs> Added;
-
-    /// <summary>Moves the subview backwards in the hierarchy, only one step</summary>
-    /// <param name="subview">The subview to send backwards</param>
-    /// <remarks>If you want to send the view all the way to the back use SendSubviewToBack.</remarks>
-    public void BringSubviewForward (View subview)
-    {
-        PerformActionForSubview (
-                                 subview,
-                                 x =>
-                                 {
-                                     int idx = _subviews.IndexOf (x);
-
-                                     if (idx + 1 < _subviews.Count)
-                                     {
-                                         _subviews.Remove (x);
-                                         _subviews.Insert (idx + 1, x);
-                                     }
-                                 }
-                                );
-    }
-
-    /// <summary>Brings the specified subview to the front so it is drawn on top of any other views.</summary>
-    /// <param name="subview">The subview to send to the front</param>
-    /// <remarks><seealso cref="SendSubviewToBack"/>.</remarks>
-    public void BringSubviewToFront (View subview)
-    {
-        PerformActionForSubview (
-                                 subview,
-                                 x =>
-                                 {
-                                     _subviews.Remove (x);
-                                     _subviews.Add (x);
-                                 }
-                                );
-    }
-
-    /// <summary>Get the top superview of a given <see cref="View"/>.</summary>
-    /// <returns>The superview view.</returns>
-    public View GetTopSuperView (View view = null, View superview = null)
-    {
-        View top = superview ?? Application.Top;
-
-        for (View v = view?.SuperView ?? this?.SuperView; v != null; v = v.SuperView)
-        {
-            top = v;
-
-            if (top == superview)
-            {
-                break;
-            }
-        }
-
-        return top;
-    }
-
-    /// <summary>Method invoked when a subview is being added to this view.</summary>
-    /// <param name="e">Event where <see cref="ViewEventArgs.View"/> is the subview being added.</param>
-    public virtual void OnAdded (SuperViewChangedEventArgs e)
-    {
-        View view = e.Child;
-        view.IsAdded = true;
-        view.OnResizeNeeded ();
-        view.Added?.Invoke (this, e);
-    }
-
-    /// <summary>Method invoked when a subview is being removed from this view.</summary>
-    /// <param name="e">Event args describing the subview being removed.</param>
-    public virtual void OnRemoved (SuperViewChangedEventArgs e)
-    {
-        View view = e.Child;
-        view.IsAdded = false;
-        view.Removed?.Invoke (this, e);
-    }
-
-    /// <summary>Removes a subview added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.</summary>
-    /// <remarks>
-    ///     <para>
-    ///         Normally Subviews will be disposed when this View is disposed. Removing a Subview causes ownership of the
-    ///         Subview's
-    ///         lifecycle to be transferred to the caller; the caller muse call <see cref="Dispose"/>.
-    ///     </para>
-    /// </remarks>
-    public virtual View Remove (View view)
-    {
-        if (view is null || _subviews is null)
-        {
-            return view;
-        }
-
-        Rectangle touched = view.Frame;
-        _subviews.Remove (view);
-        _tabIndexes.Remove (view);
-        view._superView = null;
-        view._tabIndex = -1;
-        SetNeedsLayout ();
-        SetNeedsDisplay ();
-
-        foreach (View v in _subviews)
-        {
-            if (v.Frame.IntersectsWith (touched))
-            {
-                view.SetNeedsDisplay ();
-            }
-        }
-
-        OnRemoved (new (this, view));
-
-        if (Focused == view)
-        {
-            Focused = null;
-        }
-
-        return view;
-    }
-
-    /// <summary>
-    ///     Removes all subviews (children) added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.
-    /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         Normally Subviews will be disposed when this View is disposed. Removing a Subview causes ownership of the
-    ///         Subview's
-    ///         lifecycle to be transferred to the caller; the caller must call <see cref="Dispose"/> on any Views that were
-    ///         added.
-    ///     </para>
-    /// </remarks>
-    public virtual void RemoveAll ()
-    {
-        if (_subviews is null)
-        {
-            return;
-        }
-
-        while (_subviews.Count > 0)
-        {
-            Remove (_subviews [0]);
-        }
-    }
-
-    /// <summary>Event fired when this view is removed from another.</summary>
-    public event EventHandler<SuperViewChangedEventArgs> Removed;
-
-    /// <summary>Moves the subview backwards in the hierarchy, only one step</summary>
-    /// <param name="subview">The subview to send backwards</param>
-    /// <remarks>If you want to send the view all the way to the back use SendSubviewToBack.</remarks>
-    public void SendSubviewBackwards (View subview)
-    {
-        PerformActionForSubview (
-                                 subview,
-                                 x =>
-                                 {
-                                     int idx = _subviews.IndexOf (x);
-
-                                     if (idx > 0)
-                                     {
-                                         _subviews.Remove (x);
-                                         _subviews.Insert (idx - 1, x);
-                                     }
-                                 }
-                                );
-    }
-
-    /// <summary>Sends the specified subview to the front so it is the first view drawn</summary>
-    /// <param name="subview">The subview to send to the front</param>
-    /// <remarks><seealso cref="BringSubviewToFront(View)"/>.</remarks>
-    public void SendSubviewToBack (View subview)
-    {
-        PerformActionForSubview (
-                                 subview,
-                                 x =>
-                                 {
-                                     _subviews.Remove (x);
-                                     _subviews.Insert (0, subview);
-                                 }
-                                );
-    }
-
-    private void PerformActionForSubview (View subview, Action<View> action)
-    {
-        if (_subviews.Contains (subview))
-        {
-            action (subview);
-        }
-
-        SetNeedsDisplay ();
-        subview.SetNeedsDisplay ();
-    }
-
-    #region Focus
-
-    /// <summary>Exposed as `internal` for unit tests. Indicates focus navigation direction.</summary>
-    internal enum NavigationDirection
-    {
-        /// <summary>Navigate forward.</summary>
-        Forward,
-
-        /// <summary>Navigate backwards.</summary>
-        Backward
-    }
-
-    /// <summary>Event fired when the view gets focus.</summary>
-    public event EventHandler<FocusEventArgs> Enter;
-
-    /// <summary>Event fired when the view looses focus.</summary>
-    public event EventHandler<FocusEventArgs> Leave;
-
-    private NavigationDirection _focusDirection;
-
-    internal NavigationDirection FocusDirection
-    {
-        get => SuperView?.FocusDirection ?? _focusDirection;
-        set
-        {
-            if (SuperView is { })
-            {
-                SuperView.FocusDirection = value;
-            }
-            else
-            {
-                _focusDirection = value;
-            }
-        }
-    }
-
-    private bool _hasFocus;
-
-    /// <inheritdoc/>
-    public bool HasFocus
-    {
-        set => SetHasFocus (value, this, true);
-        get => _hasFocus;
-    }
-
-    private void SetHasFocus (bool value, View view, bool force = false)
-    {
-        if (HasFocus != value || force)
-        {
-            _hasFocus = value;
-
-            if (value)
-            {
-                OnEnter (view);
-            }
-            else
-            {
-                OnLeave (view);
-            }
-
-            SetNeedsDisplay ();
-        }
-
-        // Remove focus down the chain of subviews if focus is removed
-        if (!value && Focused is { })
-        {
-            View f = Focused;
-            f.OnLeave (view);
-            f.SetHasFocus (false, view);
-            Focused = null;
-        }
-    }
-
-    /// <summary>Event fired when the <see cref="CanFocus"/> value is being changed.</summary>
-    public event EventHandler CanFocusChanged;
-
-    /// <summary>Method invoked when the <see cref="CanFocus"/> property from a view is changed.</summary>
-    public virtual void OnCanFocusChanged () { CanFocusChanged?.Invoke (this, EventArgs.Empty); }
-
-    private bool _oldCanFocus;
-    private bool _canFocus;
-
-    /// <summary>Gets or sets a value indicating whether this <see cref="View"/> can focus.</summary>
-    public bool CanFocus
-    {
-        get => _canFocus;
-        set
-        {
-            if (!_addingView && IsInitialized && SuperView?.CanFocus == false && value)
-            {
-                throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!");
-            }
-
-            if (_canFocus == value)
-            {
-                return;
-            }
-
-            _canFocus = value;
-
-            switch (_canFocus)
-            {
-                case false when _tabIndex > -1:
-                    TabIndex = -1;
-
-                    break;
-                case true when SuperView?.CanFocus == false && _addingView:
-                    SuperView.CanFocus = true;
-
-                    break;
-            }
-
-            if (_canFocus && _tabIndex == -1)
-            {
-                TabIndex = SuperView is { } ? SuperView._tabIndexes.IndexOf (this) : -1;
-            }
-
-            TabStop = _canFocus;
-
-            if (!_canFocus && SuperView?.Focused == this)
-            {
-                SuperView.Focused = null;
-            }
-
-            if (!_canFocus && HasFocus)
-            {
-                SetHasFocus (false, this);
-                SuperView?.EnsureFocus ();
-
-                if (SuperView is { Focused: null })
-                {
-                    SuperView.FocusNext ();
-
-                    if (SuperView.Focused is null && Application.Current is { })
-                    {
-                        Application.Current.FocusNext ();
-                    }
-
-                    ApplicationOverlapped.BringOverlappedTopToFront ();
-                }
-            }
-
-            if (_subviews is { } && IsInitialized)
-            {
-                foreach (View view in _subviews)
-                {
-                    if (view.CanFocus != value)
-                    {
-                        if (!value)
-                        {
-                            view._oldCanFocus = view.CanFocus;
-                            view._oldTabIndex = view._tabIndex;
-                            view.CanFocus = false;
-                            view._tabIndex = -1;
-                        }
-                        else
-                        {
-                            if (_addingView)
-                            {
-                                view._addingView = true;
-                            }
-
-                            view.CanFocus = view._oldCanFocus;
-                            view._tabIndex = view._oldTabIndex;
-                            view._addingView = false;
-                        }
-                    }
-                }
-
-                if (this is Toplevel && Application.Current.Focused != this)
-                {
-                    ApplicationOverlapped.BringOverlappedTopToFront ();
-                }
-            }
-
-            OnCanFocusChanged ();
-            SetNeedsDisplay ();
-        }
-    }
-
-    /// <summary>
-    /// Called when a view gets focus.
-    /// </summary>
-    /// <param name="view">The view that is losing focus.</param>
-    /// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
-    public virtual bool OnEnter (View view)
-    {
-        var args = new FocusEventArgs (view, this);
-        Enter?.Invoke (this, args);
-
-        if (args.Handled)
-        {
-            return true;
-        }
-
-        return false;
-    }
-
-    /// <summary>Method invoked when a view loses focus.</summary>
-    /// <param name="view">The view that is getting focus.</param>
-    /// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
-    public virtual bool OnLeave (View view)
-    {
-        var args = new FocusEventArgs (this, view);
-        Leave?.Invoke (this, args);
-
-        if (args.Handled)
-        {
-            return true;
-        }
-
-        return false;
-    }
-
-    // BUGBUG: This API is poorly defined and implemented. It does not specify what it means if THIS view is focused and has no subviews.
-    /// <summary>Returns the currently focused Subview inside this view, or null if nothing is focused.</summary>
-    /// <value>The focused.</value>
-    public View Focused { get; private set; }
-
-    // BUGBUG: This API is poorly defined and implemented. It does not specify what it means if THIS view is focused and has no subviews.
-    /// <summary>Returns the most focused Subview in the chain of subviews (the leaf view that has the focus).</summary>
-    /// <value>The most focused View.</value>
-    public View MostFocused
-    {
-        get
-        {
-            if (Focused is null)
-            {
-                return null;
-            }
-
-            View most = Focused.MostFocused;
-
-            if (most is { })
-            {
-                return most;
-            }
-
-            return Focused;
-        }
-    }
-
-    /// <summary>Causes the specified subview to have focus.</summary>
-    /// <param name="view">View.</param>
-    private void SetFocus (View view)
-    {
-        if (view is null)
-        {
-            return;
-        }
-
-        //Console.WriteLine ($"Request to focus {view}");
-        if (!view.CanFocus || !view.Visible || !view.Enabled)
-        {
-            return;
-        }
-
-        if (Focused?._hasFocus == true && Focused == view)
-        {
-            return;
-        }
-
-        if ((Focused?._hasFocus == true && Focused?.SuperView == view) || view == this)
-        {
-            if (!view._hasFocus)
-            {
-                view._hasFocus = true;
-            }
-
-            return;
-        }
-
-        // Make sure that this view is a subview
-        View c;
-
-        for (c = view._superView; c != null; c = c._superView)
-        {
-            if (c == this)
-            {
-                break;
-            }
-        }
-
-        if (c is null)
-        {
-            throw new ArgumentException ("the specified view is not part of the hierarchy of this view");
-        }
-
-        if (Focused is { })
-        {
-            Focused.SetHasFocus (false, view);
-        }
-
-        View f = Focused;
-        Focused = view;
-        Focused.SetHasFocus (true, f);
-        Focused.EnsureFocus ();
-
-        // Send focus upwards
-        if (SuperView is { })
-        {
-            SuperView.SetFocus (this);
-        }
-        else
-        {
-            SetFocus (this);
-        }
-    }
-
-    /// <summary>Causes this view to be focused and entire Superview hierarchy to have the focused order updated.</summary>
-    public void SetFocus ()
-    {
-        if (!CanBeVisible (this) || !Enabled)
-        {
-            if (HasFocus)
-            {
-                SetHasFocus (false, this);
-            }
-
-            return;
-        }
-
-        if (SuperView is { })
-        {
-            SuperView.SetFocus (this);
-        }
-        else
-        {
-            SetFocus (this);
-        }
-    }
-
-    /// <summary>
-    ///     If there is no focused subview, calls <see cref="FocusFirst"/> or <see cref="FocusLast"/> based on <see cref="FocusDirection"/>. 
-    ///     does nothing.
-    /// </summary>
-    public void EnsureFocus ()
-    {
-        if (Focused is null && _subviews?.Count > 0)
-        {
-            if (FocusDirection == NavigationDirection.Forward)
-            {
-                FocusFirst ();
-            }
-            else
-            {
-                FocusLast ();
-            }
-        }
-    }
-
-    /// <summary>
-    ///     Focuses the last focusable view in <see cref="View.TabIndexes"/> if one exists. If there are no views in <see cref="View.TabIndexes"/> then the focus is set to the view itself.
-    /// </summary>
-    public void FocusFirst (bool overlapped = false)
-    {
-        if (!CanBeVisible (this))
-        {
-            return;
-        }
-
-        if (_tabIndexes is null)
-        {
-            SuperView?.SetFocus (this);
-
-            return;
-        }
-
-        foreach (View view in _tabIndexes.Where (v => !overlapped || v.Arrangement.HasFlag (ViewArrangement.Overlapped)))
-        {
-            if (view.CanFocus && view._tabStop && view.Visible && view.Enabled)
-            {
-                SetFocus (view);
-
-                return;
-            }
-        }
-    }
-
-    /// <summary>
-    ///     Focuses the last focusable view in <see cref="View.TabIndexes"/> if one exists. If there are no views in <see cref="View.TabIndexes"/> then the focus is set to the view itself.
-    /// </summary>
-    public void FocusLast (bool overlapped = false)
-    {
-        if (!CanBeVisible (this))
-        {
-            return;
-        }
-
-        if (_tabIndexes is null)
-        {
-            SuperView?.SetFocus (this);
-
-            return;
-        }
-
-        foreach (View view in _tabIndexes.Where (v => !overlapped || v.Arrangement.HasFlag (ViewArrangement.Overlapped)).Reverse ())
-        {
-            if (view.CanFocus && view._tabStop && view.Visible && view.Enabled)
-            {
-                SetFocus (view);
-
-                return;
-            }
-        }
-    }
-
-    /// <summary>
-    ///     Focuses the previous view in <see cref="View.TabIndexes"/>. If there is no previous view, the focus is set to the view itself.
-    /// </summary>
-    /// <returns><see langword="true"/> if previous was focused, <see langword="false"/> otherwise.</returns>
-    public bool FocusPrev ()
-    {
-        if (!CanBeVisible (this))
-        {
-            return false;
-        }
-
-        FocusDirection = NavigationDirection.Backward;
-
-        if (TabIndexes is null || TabIndexes.Count == 0)
-        {
-            return false;
-        }
-
-        if (Focused is null)
-        {
-            FocusLast ();
-
-            return Focused != null;
-        }
-
-        int focusedIdx = -1;
-
-        for (int i = TabIndexes.Count; i > 0;)
-        {
-            i--;
-            View w = TabIndexes [i];
-
-            if (w.HasFocus)
-            {
-                if (w.FocusPrev ())
-                {
-                    return true;
-                }
-
-                focusedIdx = i;
-
-                continue;
-            }
-
-            if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled)
-            {
-                Focused.SetHasFocus (false, w);
-
-                // If the focused view is overlapped don't focus on the next if it's not overlapped.
-                if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && !w.Arrangement.HasFlag (ViewArrangement.Overlapped))
-                {
-                    return false;
-                }
-
-                // If the focused view is not overlapped and the next is, skip it
-                if (!Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && w.Arrangement.HasFlag (ViewArrangement.Overlapped))
-                {
-                   continue;
-                }
-
-                if (w.CanFocus && w._tabStop && w.Visible && w.Enabled)
-                {
-                    w.FocusLast ();
-                }
-
-                SetFocus (w);
-
-                return true;
-            }
-        }
-
-        // There's no prev view in tab indexes.
-        if (Focused is { })
-        {
-            // Leave Focused
-            Focused.SetHasFocus (false, this);
-
-            if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped))
-            {
-                FocusLast (true);
-                return true;
-            }
-
-            // Signal to caller no next view was found
-            Focused = null;
-        }
-
-        return false;
-    }
-
-    /// <summary>
-    ///     Focuses the next view in <see cref="View.TabIndexes"/>. If there is no next view, the focus is set to the view itself.
-    /// </summary>
-    /// <returns><see langword="true"/> if next was focused, <see langword="false"/> otherwise.</returns>
-    public bool FocusNext ()
-    {
-        if (!CanBeVisible (this))
-        {
-            return false;
-        }
-
-        FocusDirection = NavigationDirection.Forward;
-
-        if (TabIndexes is null || TabIndexes.Count == 0)
-        {
-            return false;
-        }
-
-        if (Focused is null)
-        {
-            FocusFirst ();
-
-            return Focused != null;
-        }
-
-        int focusedIdx = -1;
-
-        for (var i = 0; i < TabIndexes.Count; i++)
-        {
-            View w = TabIndexes [i];
-
-            if (w.HasFocus)
-            {
-                if (w.FocusNext ())
-                {
-                    return true;
-                }
-
-                focusedIdx = i;
-
-                continue;
-            }
-
-            if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled)
-            {
-                Focused.SetHasFocus (false, w);
-
-                // If the focused view is overlapped don't focus on the next if it's not overlapped.
-                if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && !w.Arrangement.HasFlag (ViewArrangement.Overlapped))
-                {
-                    return false;
-                }
-
-                // If the focused view is not overlapped and the next is, skip it
-                if (!Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && w.Arrangement.HasFlag (ViewArrangement.Overlapped))
-                {
-                    continue;
-                }
-
-                if (w.CanFocus && w._tabStop && w.Visible && w.Enabled)
-                {
-                    w.FocusFirst ();
-                }
-
-                SetFocus (w);
-
-                return true;
-            }
-        }
-
-        // There's no next view in tab indexes.
-        if (Focused is { })
-        {
-            // Leave Focused
-            Focused.SetHasFocus (false, this);
-
-            if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped))
-            {
-                FocusFirst (true);
-                return true;
-            }
-
-            // Signal to caller no next view was found
-            Focused = null;
-        }
-
-        return false;
-    }
-
-    private View GetMostFocused (View view)
-    {
-        if (view is null)
-        {
-            return null;
-        }
-
-        return view.Focused is { } ? GetMostFocused (view.Focused) : view;
-    }
-
-    /// <summary>
-    /// Gets or sets the cursor style to be used when the view is focused. The default is <see cref="CursorVisibility.Invisible"/>.
-    /// </summary>
-    public CursorVisibility CursorVisibility { get; set; } = CursorVisibility.Invisible;
-
-    /// <summary>
-    ///     Positions the cursor in the right position based on the currently focused view in the chain.
-    /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         Views that are focusable should override <see cref="PositionCursor"/> to make sure that the cursor is
-    ///         placed in a location that makes sense. Some terminals do not have a way of hiding the cursor, so it can be
-    ///         distracting to have the cursor left at the last focused view. So views should make sure that they place the
-    ///         cursor in a visually sensible place. The default implementation of <see cref="PositionCursor"/> will place the
-    ///         cursor at either the hotkey (if defined) or <c>0,0</c>.
-    ///     </para>
-    /// </remarks>
-    /// <returns>Viewport-relative cursor position. Return <see langword="null"/> to ensure the cursor is not visible.</returns>
-    public virtual Point? PositionCursor ()
-    {
-        if (IsInitialized && CanFocus && HasFocus)
-        {
-            // By default, position the cursor at the hotkey (if any) or 0, 0.
-            Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0);
-        }
-
-        // Returning null will hide the cursor.
-        return null;
-    }
-
-    #endregion Focus
-}

+ 5 - 20
UICatalog/Scenarios/ViewExperiments.cs

@@ -27,6 +27,8 @@ public class ViewExperiments : Scenario
             Title = "View1",
             ColorScheme = Colors.ColorSchemes ["Base"],
             Id = "View1",
+            ShadowStyle = ShadowStyle.Transparent,
+            BorderStyle = LineStyle.Double,
             CanFocus = true, // Can't drag without this? BUGBUG
             Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped
         };
@@ -46,16 +48,7 @@ public class ViewExperiments : Scenario
 
         //app.Add (view);
 
-        view.Margin.Thickness = new (0);
-        view.Margin.ColorScheme = Colors.ColorSchemes ["Toplevel"];
-        view.Margin.Data = "Margin";
-        view.Border.Thickness = new (1);
-        view.Border.LineStyle = LineStyle.Double;
-        view.Border.ColorScheme = view.ColorScheme;
-        view.Border.Data = "Border";
-        view.Padding.Thickness = new (0);
-        view.Padding.ColorScheme = Colors.ColorSchemes ["Error"];
-        view.Padding.Data = "Padding";
+        view.BorderStyle = LineStyle.Double;
 
         var view2 = new View
         {
@@ -66,6 +59,8 @@ public class ViewExperiments : Scenario
             Title = "View2",
             ColorScheme = Colors.ColorSchemes ["Base"],
             Id = "View2",
+            ShadowStyle = ShadowStyle.Transparent,
+            BorderStyle = LineStyle.Double,
             CanFocus = true, // Can't drag without this? BUGBUG
             Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped
         };
@@ -85,16 +80,6 @@ public class ViewExperiments : Scenario
         view2.Add (button);
 
         view2.Add (button);
-        view2.Margin.Thickness = new (0);
-        view2.Margin.ColorScheme = Colors.ColorSchemes ["Toplevel"];
-        view2.Margin.Data = "Margin";
-        view2.Border.Thickness = new (1);
-        view2.Border.LineStyle = LineStyle.Double;
-        view2.Border.ColorScheme = view2.ColorScheme;
-        view2.Border.Data = "Border";
-        view2.Padding.Thickness = new (0);
-        view2.Padding.ColorScheme = Colors.ColorSchemes ["Error"];
-        view2.Padding.Data = "Padding";
 
         button = new ()
         {