Browse Source

Revert "Rebuildling TabView - WIP"

This reverts commit 77ae7ae7df6ecebe6a9382a4d555260532ef0308.
Tig 9 months ago
parent
commit
e6180b63de

+ 2 - 6
Terminal.Gui/Views/Tab.cs

@@ -1,6 +1,4 @@
 #nullable enable
 #nullable enable
-using System.Net.Security;
-
 namespace Terminal.Gui;
 namespace Terminal.Gui;
 
 
 /// <summary>A single tab in a <see cref="TabView"/>.</summary>
 /// <summary>A single tab in a <see cref="TabView"/>.</summary>
@@ -13,9 +11,7 @@ public class Tab : View
     {
     {
         BorderStyle = LineStyle.Rounded;
         BorderStyle = LineStyle.Rounded;
         CanFocus = true;
         CanFocus = true;
-        TabStop = TabBehavior.TabStop;
-        Width = Dim.Auto (DimAutoStyle.Text);
-        SuperViewRendersLineCanvas = true;
+        TabStop = TabBehavior.NoStop;
     }
     }
 
 
     /// <summary>The text to display in a <see cref="TabView"/>.</summary>
     /// <summary>The text to display in a <see cref="TabView"/>.</summary>
@@ -30,7 +26,7 @@ public class Tab : View
         }
         }
     }
     }
 
 
-    /// <summary>The View that will be made visible in the <see cref="TabView"/> content area when the tab is selected.</summary>
+    /// <summary>The control to display when the tab is selected.</summary>
     /// <value></value>
     /// <value></value>
     public View? View { get; set; }
     public View? View { get; set; }
 }
 }

+ 10 - 10
Terminal.Gui/Views/TabChangedEventArgs.cs

@@ -1,20 +1,20 @@
 namespace Terminal.Gui;
 namespace Terminal.Gui;
 
 
-/// <summary>Describes a change in <see cref="TabView.SelectedTabIndex"/></summary>
+/// <summary>Describes a change in <see cref="TabView.SelectedTab"/></summary>
 public class TabChangedEventArgs : EventArgs
 public class TabChangedEventArgs : EventArgs
 {
 {
     /// <summary>Documents a tab change</summary>
     /// <summary>Documents a tab change</summary>
-    /// <param name="oldTabIndex"></param>
-    /// <param name="newTabIndex"></param>
-    public TabChangedEventArgs (int? oldTabIndex, int? newTabIndex)
+    /// <param name="oldTab"></param>
+    /// <param name="newTab"></param>
+    public TabChangedEventArgs (Tab oldTab, Tab newTab)
     {
     {
-        OldTabIndex = oldTabIndex;
-        NewTabIndex = newTabIndex;
+        OldTab = oldTab;
+        NewTab = newTab;
     }
     }
 
 
-    /// <summary>The currently selected tab.</summary>
-    public int? NewTabIndex { get; }
+    /// <summary>The currently selected tab. May be null</summary>
+    public Tab NewTab { get; }
 
 
-    /// <summary>The previously selected tab.</summary>
-    public int? OldTabIndex{ get; }
+    /// <summary>The previously selected tab. May be null</summary>
+    public Tab OldTab { get; }
 }
 }

+ 1037 - 413
Terminal.Gui/Views/TabView.cs

@@ -1,36 +1,42 @@
 #nullable enable
 #nullable enable
-using System.Linq;
-using static Terminal.Gui.SpinnerStyle;
-using static Unix.Terminal.Delegates;
-
 namespace Terminal.Gui;
 namespace Terminal.Gui;
 
 
 /// <summary>Control that hosts multiple sub views, presenting a single one at once.</summary>
 /// <summary>Control that hosts multiple sub views, presenting a single one at once.</summary>
-public class TabView : View, IDesignable
+public class TabView : View
 {
 {
     /// <summary>The default <see cref="MaxTabTextWidth"/> to set on new <see cref="TabView"/> controls.</summary>
     /// <summary>The default <see cref="MaxTabTextWidth"/> to set on new <see cref="TabView"/> controls.</summary>
     public const uint DefaultMaxTabTextWidth = 30;
     public const uint DefaultMaxTabTextWidth = 30;
 
 
-    /// <summary>This SubView is the 2 or 3 line control that represents the actual tabs themselves.</summary>
-    private readonly TabRowView _tabRowView;
+    /// <summary>
+    ///     This sub view is the main client area of the current tab.  It hosts the <see cref="Tab.View"/> of the tab, the
+    ///     <see cref="SelectedTab"/>.
+    /// </summary>
+    private readonly View _contentView;
+
+    private readonly List<Tab> _tabs = new ();
+
+    /// <summary>This sub view is the 2 or 3 line control that represents the actual tabs themselves.</summary>
+    private readonly TabRowView _tabsBar;
 
 
-    // private TabToRender []? _tabLocations;
+    private Tab? _selectedTab;
+    private TabToRender []? _tabLocations;
+    private int _tabScrollOffset;
 
 
     /// <summary>Initializes a <see cref="TabView"/> class.</summary>
     /// <summary>Initializes a <see cref="TabView"/> class.</summary>
     public TabView ()
     public TabView ()
     {
     {
         CanFocus = true;
         CanFocus = true;
         TabStop = TabBehavior.TabStop; // Because TabView has focusable subviews, it must be a TabGroup
         TabStop = TabBehavior.TabStop; // Because TabView has focusable subviews, it must be a TabGroup
-
-        Width = Dim.Fill ();
-        Height = Dim.Auto (minimumContentDim: GetTabHeight (!Style.TabsOnBottom));
-
-        _tabRowView = new TabRowView ();
-        _tabRowView.Selecting += _tabRowView_Selecting;
-        base.Add (_tabRowView);
-
+        _tabsBar = new TabRowView (this);
+        _contentView = new View ()
+        {
+            //Id = "TabView._contentView",
+        };
         ApplyStyleChanges ();
         ApplyStyleChanges ();
 
 
+        base.Add (_tabsBar);
+        base.Add (_contentView);
+
         // Things this view knows how to do
         // Things this view knows how to do
         AddCommand (Command.Left, () => SwitchTabBy (-1));
         AddCommand (Command.Left, () => SwitchTabBy (-1));
 
 
@@ -40,8 +46,8 @@ public class TabView : View, IDesignable
                     Command.LeftStart,
                     Command.LeftStart,
                     () =>
                     () =>
                     {
                     {
-                        FirstVisibleTabIndex = 0;
-                        SelectedTabIndex = 0;
+                        TabScrollOffset = 0;
+                        SelectedTab = Tabs.FirstOrDefault ()!;
 
 
                         return true;
                         return true;
                     }
                     }
@@ -51,8 +57,8 @@ public class TabView : View, IDesignable
                     Command.RightEnd,
                     Command.RightEnd,
                     () =>
                     () =>
                     {
                     {
-                        FirstVisibleTabIndex = Tabs.Count - 1;
-                        SelectedTabIndex = Tabs.Count - 1;
+                        TabScrollOffset = Tabs.Count - 1;
+                        SelectedTab = Tabs.LastOrDefault ()!;
 
 
                         return true;
                         return true;
                     }
                     }
@@ -62,8 +68,8 @@ public class TabView : View, IDesignable
                     Command.PageDown,
                     Command.PageDown,
                     () =>
                     () =>
                     {
                     {
-                        // FirstVisibleTabIndex += _tabLocations!.Length;
-                        SelectedTabIndex = FirstVisibleTabIndex;
+                        TabScrollOffset += _tabLocations!.Length;
+                        SelectedTab = Tabs.ElementAt (TabScrollOffset);
 
 
                         return true;
                         return true;
                     }
                     }
@@ -73,67 +79,13 @@ public class TabView : View, IDesignable
                     Command.PageUp,
                     Command.PageUp,
                     () =>
                     () =>
                     {
                     {
-                        //  FirstVisibleTabIndex -= _tabLocations!.Length;
-                        SelectedTabIndex = FirstVisibleTabIndex;
+                        TabScrollOffset -= _tabLocations!.Length;
+                        SelectedTab = Tabs.ElementAt (TabScrollOffset);
 
 
                         return true;
                         return true;
                     }
                     }
                    );
                    );
 
 
-        AddCommand (Command.ScrollLeft, () =>
-                                        {
-                                            var visibleTabs = GetTabsThatCanBeVisible (Viewport).ToArray ();
-                                            int? first = visibleTabs.FirstOrDefault ();
-
-                                            if (first > 0)
-                                            {
-                                                int scroll = -_tabRowView.Tabs.ToArray () [first.Value].Frame.Width;
-                                                _tabRowView.Viewport = _tabRowView.Viewport with { X = _tabRowView.Viewport.X + scroll };
-                                                SetNeedsLayout ();
-                                                FirstVisibleTabIndex--;
-                                                return true;
-                                            }
-
-                                            return false;
-                                        });
-
-        AddCommand (Command.ScrollRight, () =>
-                                         {
-                                             var visibleTabs = GetTabsThatCanBeVisible (Viewport).ToArray ();
-                                             int? last = visibleTabs.LastOrDefault ();
-
-                                             if (last is { })
-                                             {
-                                                 _tabRowView.ScrollHorizontal (_tabRowView.Tabs.ToArray () [last.Value + 1].Frame.Width);
-                                                 SetNeedsLayout ();
-                                                 FirstVisibleTabIndex++;
-                                                 return true;
-                                             }
-
-                                             return false;
-                                         });
-
-        //// Space or single-click - Raise Selecting
-        //AddCommand (Command.Select, (ctx) =>
-        //                            {
-        //                                //if (RaiseSelecting (ctx) is true)
-        //                                //{
-        //                                //    return true;
-        //                                //}
-
-        //                                if (ctx.Data is Tab tab)
-        //                                {
-        //                                    int? current = SelectedTabIndex;
-        //                                    SelectedTabIndex = _tabRowView.Tabs.ToArray ().IndexOf (tab);
-        //                                    SetNeedsDraw ();
-
-        //                                    // e.Cancel = HasFocus;
-        //                                    return true;
-        //                                }
-
-        //                                return false;
-        //                            });
-
         // Default keybindings for this view
         // 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);
@@ -143,108 +95,65 @@ public class TabView : View, IDesignable
         KeyBindings.Add (Key.PageUp, Command.PageUp);
         KeyBindings.Add (Key.PageUp, Command.PageUp);
     }
     }
 
 
-    private void _tabRowView_Selecting (object? sender, CommandEventArgs e)
-    {
-        if (e.Context.Data is int tabIndex)
-        {
-            int? current = SelectedTabIndex;
-            SelectedTabIndex = tabIndex;
-            Layout ();
-            e.Cancel = true;
-        }
-    }
-
-    /// <inheritdoc />
-    protected override void OnSubviewLayout (LayoutEventArgs args)
-    {
-        _tabRowView.CalcContentSize ();
-    }
-
-    /// <inheritdoc />
-    protected override void OnSubviewsLaidOut (LayoutEventArgs args)
-    {
-        // hide all that can't fit
-        var visibleTabs = GetTabsThatCanBeVisible (Viewport).ToArray ();
-
-        for (var index = 0; index < _tabRowView.Tabs.ToArray ().Length; index++)
-        {
-            Tab tab = _tabRowView.Tabs.ToArray () [index];
-            tab.Visible = visibleTabs.Contains (index);
-        }
-    }
-
-    /// <inheritdoc />
-    public bool EnableForDesign ()
-    {
-        AddTab (new () { Text = "Tab_1", Id = "tab1", View = new Label { Text = "Label in Tab1" } }, false);
-        AddTab (new () { Text = "Tab _2", Id = "tab2", View = new TextField { Text = "TextField in Tab2", Width = 10 } }, false);
-        AddTab (new () { Text = "Tab _Three", Id = "tab3", View = new Label { Text = "Label in Tab3" } }, false);
-        AddTab (new () { Text = "Tab _Quattro", Id = "tab4", View = new TextField { Text = "TextField in Tab4", Width = 10 } }, false);
-
-        return true;
-    }
-
     /// <summary>
     /// <summary>
     ///     The maximum number of characters to render in a Tab header.  This prevents one long tab from pushing out all
     ///     The maximum number of characters to render in a Tab header.  This prevents one long tab from pushing out all
     ///     the others.
     ///     the others.
     /// </summary>
     /// </summary>
     public uint MaxTabTextWidth { get; set; } = DefaultMaxTabTextWidth;
     public uint MaxTabTextWidth { get; set; } = DefaultMaxTabTextWidth;
 
 
-    private int? _selectedTabIndex;
-
     /// <summary>The currently selected member of <see cref="Tabs"/> chosen by the user.</summary>
     /// <summary>The currently selected member of <see cref="Tabs"/> chosen by the user.</summary>
     /// <value></value>
     /// <value></value>
-    public int? SelectedTabIndex
+    public Tab? SelectedTab
     {
     {
-        get => _selectedTabIndex;
+        get => _selectedTab;
         set
         set
         {
         {
-            // If value is outside the range of Tabs, throw an exception
-            if (value < 0 || value >= Tabs.Count)
-            {
-                throw new ArgumentOutOfRangeException (nameof (value), value, @"SelectedTab the range of Tabs.");
-            }
-
-            if (value == _selectedTabIndex)
-            {
-                return;
-            }
-
-            int? old = _selectedTabIndex;
+            UnSetCurrentTabs ();
 
 
-            // Get once to avoid multiple enumerations
-            Tab [] tabs = _tabRowView.Tabs.ToArray ();
+            Tab? old = _selectedTab;
 
 
-            if (_selectedTabIndex is { } && tabs [_selectedTabIndex.Value].View is { })
+            if (_selectedTab is { })
             {
             {
-                Remove (tabs [_selectedTabIndex.Value].View);
+                if (_selectedTab.View is { })
+                {
+                    _selectedTab.View.CanFocusChanged -= ContentViewCanFocus!;
+                    // remove old content
+                    _contentView.Remove (_selectedTab.View);
+                }
             }
             }
 
 
-            _selectedTabIndex = value;
+            _selectedTab = value;
 
 
-            if (_selectedTabIndex is { } && tabs [_selectedTabIndex.Value].View is { })
+            // add new content
+            if (_selectedTab?.View != null)
             {
             {
-                Add (tabs [_selectedTabIndex.Value].View);
+                _selectedTab.View.CanFocusChanged += ContentViewCanFocus!;
+                _contentView.Add (_selectedTab.View);
+                // _contentView.Id = $"_contentView for {_selectedTab.DisplayText}";
             }
             }
 
 
+            ContentViewCanFocus (null!, null!);
+
             EnsureSelectedTabIsVisible ();
             EnsureSelectedTabIsVisible ();
 
 
-            if (_selectedTabIndex is { })
+            if (old != _selectedTab)
             {
             {
-                ApplyStyleChanges ();
-
-                if (HasFocus)
+                if (old?.HasFocus == true)
                 {
                 {
-                    tabs [_selectedTabIndex.Value].View.SetFocus ();
+                    SelectedTab?.SetFocus ();
                 }
                 }
-            }
 
 
-            OnSelectedTabIndexChanged (old, _selectedTabIndex!);
-            SelectedTabChanged?.Invoke (this, new TabChangedEventArgs (old, _selectedTabIndex));
+                OnSelectedTabChanged (old!, _selectedTab!);
+            }
             SetNeedsLayout ();
             SetNeedsLayout ();
         }
         }
     }
     }
 
 
+    private void ContentViewCanFocus (object sender, EventArgs eventArgs)
+    {
+        _contentView.CanFocus = _contentView.Subviews.Count (v => v.CanFocus) > 0;
+    }
+
     private TabStyle _style = new ();
     private TabStyle _style = new ();
 
 
     /// <summary>Render choices for how to display tabs.  After making changes, call <see cref="ApplyStyleChanges()"/>.</summary>
     /// <summary>Render choices for how to display tabs.  After making changes, call <see cref="ApplyStyleChanges()"/>.</summary>
@@ -258,7 +167,6 @@ public class TabView : View, IDesignable
             {
             {
                 return;
                 return;
             }
             }
-
             _style = value;
             _style = value;
             SetNeedsLayout ();
             SetNeedsLayout ();
         }
         }
@@ -266,279 +174,342 @@ public class TabView : View, IDesignable
 
 
     /// <summary>All tabs currently hosted by the control.</summary>
     /// <summary>All tabs currently hosted by the control.</summary>
     /// <value></value>
     /// <value></value>
-    public IReadOnlyCollection<Tab> Tabs => _tabRowView.Tabs.ToArray ().AsReadOnly ();
-
-    private int _firstVisibleTabIndex;
-
-    /// <summary>Gets or sets the index of first visible tab. This enables horizontal scrolling of the tabs.</summary>
-    /// <remarks>
-    ///     <para>
-    ///         On set, if the value is less than 0, it will be set to 0.  If the value is greater than the number of tabs
-    ///         it will be set to the last tab index.
-    ///     </para>
-    /// </remarks>
-    public int FirstVisibleTabIndex
+    public IReadOnlyCollection<Tab> Tabs => _tabs.AsReadOnly ();
+
+    /// <summary>When there are too many tabs to render, this indicates the first tab to render on the screen.</summary>
+    /// <value></value>
+    public int TabScrollOffset
     {
     {
-        get => _firstVisibleTabIndex;
+        get => _tabScrollOffset;
         set
         set
         {
         {
-            _firstVisibleTabIndex = Math.Max (Math.Min (value, Tabs.Count - 1), 0);
-            ;
+            _tabScrollOffset = EnsureValidScrollOffsets (value);
             SetNeedsLayout ();
             SetNeedsLayout ();
         }
         }
     }
     }
 
 
     /// <summary>Adds the given <paramref name="tab"/> to <see cref="Tabs"/>.</summary>
     /// <summary>Adds the given <paramref name="tab"/> to <see cref="Tabs"/>.</summary>
     /// <param name="tab"></param>
     /// <param name="tab"></param>
-    /// <param name="andSelect">True to make the newly added Tab the <see cref="SelectedTabIndex"/>.</param>
+    /// <param name="andSelect">True to make the newly added Tab the <see cref="SelectedTab"/>.</param>
     public void AddTab (Tab tab, bool andSelect)
     public void AddTab (Tab tab, bool andSelect)
     {
     {
-        // Ok to use Subviews here instead of Tabs
-        if (_tabRowView.Subviews.Contains (tab))
+        if (_tabs.Contains (tab))
         {
         {
             return;
             return;
         }
         }
 
 
-        // Add to the TabRowView as a subview
-        _tabRowView.Add (tab);
+        _tabs.Add (tab);
+        _tabsBar.Add (tab);
 
 
-        if (_tabRowView.Tabs.Count () == 1 || andSelect)
+        if (SelectedTab is null || andSelect)
         {
         {
-            SelectedTabIndex = _tabRowView.Tabs.Count () - 1;
+            SelectedTab = tab;
 
 
             EnsureSelectedTabIsVisible ();
             EnsureSelectedTabIsVisible ();
 
 
-            if (HasFocus)
-            {
-                tab.View?.SetFocus ();
-            }
+            tab.View?.SetFocus ();
         }
         }
 
 
-        ApplyStyleChanges ();
         SetNeedsLayout ();
         SetNeedsLayout ();
     }
     }
 
 
-
     /// <summary>
     /// <summary>
-    ///     Removes the given <paramref name="tab"/> from <see cref="Tabs"/>. Caller is responsible for disposing the
-    ///     tab's hosted <see cref="Tab.View"/> if appropriate.
+    ///     Updates the control to use the latest state settings in <see cref="Style"/>. This can change the size of the
+    ///     client area of the tab (for rendering the selected tab's content).  This method includes a call to
+    ///     <see cref="View.SetNeedsDraw()"/>.
     /// </summary>
     /// </summary>
-    /// <param name="tab"></param>
-    public void RemoveTab (Tab? tab)
+    public void ApplyStyleChanges ()
     {
     {
-        if (tab is null || !_tabRowView.Subviews.Contains (tab))
-        {
-            return;
-        }
+        _contentView.BorderStyle = Style.ShowBorder ? LineStyle.Single : LineStyle.None;
+        _contentView.Width = Dim.Fill ();
 
 
-        int idx = _tabRowView.Tabs.ToArray ().IndexOf (tab);
-        if (idx == SelectedTabIndex)
+        if (Style.TabsOnBottom)
         {
         {
-            SelectedTabIndex = null;
-        }
+            // Tabs are along the bottom so just dodge the border
+            if (Style.ShowBorder)
+            {
+                _contentView.Border.Thickness = new Thickness (1, 1, 1, 0);
+            }
 
 
-        _tabRowView.Remove (tab);
+            _contentView.Y = 0;
 
 
-        // Get once to avoid multiple enumerations
-        Tab [] tabs = _tabRowView.Tabs.ToArray ();
+            int tabHeight = GetTabHeight (false);
 
 
-        if (SelectedTabIndex is null)
-        {
-            // Either no tab was previously selected or the selected tab was removed
+            // Fill client area leaving space at bottom for tabs
+            _contentView.Height = Dim.Fill (tabHeight);
 
 
-            // select the tab closest to the one that disappeared
-            int toSelect = Math.Max (idx - 1, 0);
+            _tabsBar.Height = tabHeight;
 
 
-            if (toSelect < tabs.Length)
-            {
-                SelectedTabIndex = toSelect;
-            }
-            else
+            _tabsBar.Y = Pos.Bottom (_contentView);
+        }
+        else
+        {
+            // Tabs are along the top
+            if (Style.ShowBorder)
             {
             {
-                SelectedTabIndex = tabs.Length - 1;
+                _contentView.Border.Thickness = new Thickness (1, 0, 1, 1);
             }
             }
-        }
 
 
-        if (SelectedTabIndex > tabs.Length - 1)
-        {
-            // Removing the tab, caused the selected tab to be out of range
-            SelectedTabIndex = tabs.Length - 1;
+            _tabsBar.Y = 0;
+
+            int tabHeight = GetTabHeight (true);
+
+            //move content down to make space for tabs
+            _contentView.Y = Pos.Bottom (_tabsBar);
+
+            // Fill client area leaving space at bottom for border
+            _contentView.Height = Dim.Fill ();
+
+            // The top tab should be 2 or 3 rows high and on the top
+
+            _tabsBar.Height = tabHeight;
+
+            // Should be able to just use 0 but switching between top/bottom tabs repeatedly breaks in ValidatePosDim if just using the absolute value 0
         }
         }
 
 
-        EnsureSelectedTabIsVisible ();
         SetNeedsLayout ();
         SetNeedsLayout ();
     }
     }
 
 
-    /// <summary>
-    ///     Applies the settings in <see cref="Style"/>. This can change the dimensions of
-    ///     <see cref="Tab.View"/> (for rendering the selected tab's content). This method includes a call to
-    ///     <see cref="View.SetNeedsDraw()"/>.
-    /// </summary>
-    public void ApplyStyleChanges ()
+    /// <summary>Updates <see cref="TabScrollOffset"/> to ensure that <see cref="SelectedTab"/> is visible.</summary>
+    public void EnsureSelectedTabIsVisible ()
     {
     {
-        // Get once to avoid multiple enumerations
-        Tab [] tabs = _tabRowView.Tabs.ToArray ();
-
-        View? selectedView = null;
-
-        if (SelectedTabIndex is { })
+        if (!IsInitialized || SelectedTab is null)
         {
         {
-            selectedView = tabs [SelectedTabIndex.Value].View;
+            return;
         }
         }
 
 
-        if (selectedView is { })
+        // if current viewport does not include the selected tab
+        if (!CalculateViewport (Viewport).Any (r => Equals (SelectedTab, r.Tab)))
         {
         {
-            selectedView.BorderStyle = Style.ShowBorder ? LineStyle.Single : LineStyle.None;
-            selectedView.Width = Dim.Fill ();
+            // Set scroll offset so the first tab rendered is the
+            TabScrollOffset = Math.Max (0, Tabs.IndexOf (SelectedTab));
         }
         }
+    }
 
 
-        int tabHeight = GetTabHeight (!Style.TabsOnBottom);
+    /// <summary>Updates <see cref="TabScrollOffset"/> to be a valid index of <see cref="Tabs"/>.</summary>
+    /// <param name="value">The value to validate.</param>
+    /// <remarks>Changes will not be immediately visible in the display until you call <see cref="View.SetNeedsDraw()"/>.</remarks>
+    /// <returns>The valid <see cref="TabScrollOffset"/> for the given value.</returns>
+    public int EnsureValidScrollOffsets (int value) { return Math.Max (Math.Min (value, Tabs.Count - 1), 0); }
 
 
-        if (Style.TabsOnBottom)
+    /// <inheritdoc />
+    protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedView)
+    {
+        if (SelectedTab is { } && !_contentView.CanFocus && focusedView == this)
         {
         {
-            _tabRowView.Height = tabHeight;
-            _tabRowView.Y = Pos.AnchorEnd ();
-
-            if (selectedView is { })
-            {
-                // Tabs are along the bottom so just dodge the border
-                if (Style.ShowBorder && selectedView?.Border is { })
-                {
-                    selectedView.Border.Thickness = new Thickness (1, 1, 1, 0);
-                }
+            SelectedTab?.SetFocus ();
 
 
-                // Fill client area leaving space at bottom for tabs
-                selectedView!.Y = 0;
-                selectedView.Height = Dim.Fill (tabHeight);
-            }
+            return;
         }
         }
-        else
-        {
-            // Tabs are along the top
-            _tabRowView.Height = tabHeight;
-            _tabRowView.Y = 0;
-
-            if (selectedView is { })
-            {
-                if (Style.ShowBorder && selectedView.Border is { })
-                {
-                    selectedView.Border.Thickness = new Thickness (1, 0, 1, 1);
-                }
 
 
+        base.OnHasFocusChanged (newHasFocus, previousFocusedView, focusedView);
+    }
 
 
-                //move content down to make space for tabs
-                selectedView.Y = Pos.Bottom (_tabRowView);
+    /// <inheritdoc/>
+    protected override bool OnDrawingContent ()
+    {
+        if (Tabs.Any ())
+        {
+            // Region savedClip = SetClip ();
+            _tabsBar.Draw ();
+            _contentView.SetNeedsDraw ();
+            _contentView.Draw ();
 
 
-                // Fill client area leaving space at bottom for border
-                selectedView.Height = Dim.Fill ();
-            }
+            //if (Driver is { })
+            //{
+            //    Driver.Clip = savedClip;
+            //}
         }
         }
 
 
-        SetNeedsLayout ();
+        return true;
     }
     }
 
 
-    /// <summary>Updates <see cref="FirstVisibleTabIndex"/> to ensure that <see cref="SelectedTabIndex"/> is visible.</summary>
-    public void EnsureSelectedTabIsVisible ()
+    /// <summary>
+    ///     Removes the given <paramref name="tab"/> from <see cref="Tabs"/>. Caller is responsible for disposing the
+    ///     tab's hosted <see cref="Tab.View"/> if appropriate.
+    /// </summary>
+    /// <param name="tab"></param>
+    public void RemoveTab (Tab? tab)
     {
     {
-        if (SelectedTabIndex is null)
+        if (tab is null || !_tabs.Contains (tab))
         {
         {
             return;
             return;
         }
         }
 
 
-        // Get once to avoid multiple enumerations
-        Tab [] tabs = _tabRowView.Tabs.ToArray ();
-        View? selectedView = tabs [SelectedTabIndex.Value].View;
+        // what tab was selected before closing
+        int idx = _tabs.IndexOf (tab);
 
 
-        if (selectedView is null)
-        {
-            return;
-        }
+        _tabs.Remove (tab);
 
 
-        // if current viewport does not include the selected tab
-        if (!GetTabsThatCanBeVisible (Viewport).Any (r => Equals (SelectedTabIndex.Value, r)))
+        // if the currently selected tab is no longer a member of Tabs
+        if (SelectedTab is null || !Tabs.Contains (SelectedTab))
         {
         {
-            // Set scroll offset so the first tab rendered is the
-            FirstVisibleTabIndex = Math.Max (0, SelectedTabIndex.Value);
+            // select the tab closest to the one that disappeared
+            int toSelect = Math.Max (idx - 1, 0);
+
+            if (toSelect < Tabs.Count)
+            {
+                SelectedTab = Tabs.ElementAt (toSelect);
+            }
+            else
+            {
+                SelectedTab = Tabs.LastOrDefault ();
+            }
         }
         }
+
+        EnsureSelectedTabIsVisible ();
+        SetNeedsLayout ();
     }
     }
 
 
-    /// <summary>Event for when <see cref="SelectedTabIndex"/> changes.</summary>
+    /// <summary>Event for when <see cref="SelectedTab"/> changes.</summary>
     public event EventHandler<TabChangedEventArgs>? SelectedTabChanged;
     public event EventHandler<TabChangedEventArgs>? SelectedTabChanged;
 
 
     /// <summary>
     /// <summary>
-    ///     Changes the <see cref="SelectedTabIndex"/> by the given <paramref name="amount"/>. Positive for right, negative for
-    ///     left. If no tab is currently selected then the first tab will become selected.
+    ///     Changes the <see cref="SelectedTab"/> by the given <paramref name="amount"/>. Positive for right, negative for
+    ///     left.  If no tab is currently selected then the first tab will become selected.
     /// </summary>
     /// </summary>
     /// <param name="amount"></param>
     /// <param name="amount"></param>
-    /// <returns><see langword="true"/> if a change was made.</returns>
     public bool SwitchTabBy (int amount)
     public bool SwitchTabBy (int amount)
     {
     {
-
-        // Get once to avoid multiple enumerations
-        Tab [] tabs = _tabRowView.Tabs.ToArray ();
-
-        if (tabs.Length == 0)
+        if (Tabs.Count == 0)
         {
         {
             return false;
             return false;
         }
         }
 
 
-        int? currentIdx = SelectedTabIndex;
-
         // if there is only one tab anyway or nothing is selected
         // if there is only one tab anyway or nothing is selected
-        if (tabs.Length == 1)
+        if (Tabs.Count == 1 || SelectedTab is null)
         {
         {
-            SelectedTabIndex = 0;
+            SelectedTab = Tabs.ElementAt (0);
 
 
-            return SelectedTabIndex != currentIdx;
+            return SelectedTab is { };
         }
         }
 
 
+        int currentIdx = Tabs.IndexOf (SelectedTab);
+
         // Currently selected tab has vanished!
         // Currently selected tab has vanished!
-        if (currentIdx is null)
+        if (currentIdx == -1)
         {
         {
-            SelectedTabIndex = 0;
-
+            SelectedTab = Tabs.ElementAt (0);
             return true;
             return true;
         }
         }
 
 
-        int newIdx = Math.Max (0, Math.Min (currentIdx.Value + amount, tabs.Length - 1));
+        int newIdx = Math.Max (0, Math.Min (currentIdx + amount, Tabs.Count - 1));
 
 
         if (newIdx == currentIdx)
         if (newIdx == currentIdx)
         {
         {
             return false;
             return false;
         }
         }
 
 
-        SelectedTabIndex = newIdx;
+        SelectedTab = _tabs [newIdx];
+
+        EnsureSelectedTabIsVisible ();
 
 
         return true;
         return true;
     }
     }
 
 
-    /// <summary>Called when the <see cref="SelectedTabIndex"/> has changed.</summary>
-    protected virtual void OnSelectedTabIndexChanged (int? oldTabIndex, int? newTabIndex) { }
+    /// <summary>
+    ///     Event fired when a <see cref="Tab"/> is clicked.  Can be used to cancel navigation, show context menu (e.g. on
+    ///     right click) etc.
+    /// </summary>
+    public event EventHandler<TabMouseEventArgs>? TabClicked;
+
+    /// <summary>Disposes the control and all <see cref="Tabs"/>.</summary>
+    /// <param name="disposing"></param>
+    protected override void Dispose (bool disposing)
+    {
+        base.Dispose (disposing);
+
+        // The selected tab will automatically be disposed but
+        // any tabs not visible will need to be manually disposed
+
+        foreach (Tab tab in Tabs)
+        {
+            if (!Equals (SelectedTab, tab))
+            {
+                tab.View?.Dispose ();
+            }
+        }
+    }
+
+    /// <summary>Raises the <see cref="SelectedTabChanged"/> event.</summary>
+    protected virtual void OnSelectedTabChanged (Tab oldTab, Tab newTab)
+    {
+        SelectedTabChanged?.Invoke (this, new TabChangedEventArgs (oldTab, newTab));
+    }
 
 
-    /// <summary>Returns which tabs will be visible given the dimensions of the TabView, which tab is selected, and how the tabs have been scrolled.</summary>
-    /// <paramref name="bounds">Same as this.Frame.</paramref>
+    /// <summary>Returns which tabs to render at each x location.</summary>
     /// <returns></returns>
     /// <returns></returns>
-    private IEnumerable<int> GetTabsThatCanBeVisible (Rectangle bounds)
+    private IEnumerable<TabToRender> CalculateViewport (Rectangle bounds)
     {
     {
-        var curWidth = 1;
-        View? prevTab = null;
+        UnSetCurrentTabs ();
 
 
-        // Get once to avoid multiple enumerations
-        Tab [] tabs = _tabRowView.Tabs.ToArray ();
+        var i = 1;
+        View? prevTab = null;
 
 
         // Starting at the first or scrolled to tab
         // Starting at the first or scrolled to tab
-        for (int i = FirstVisibleTabIndex; i < tabs.Length; i++)
+        foreach (Tab tab in Tabs.Skip (TabScrollOffset))
         {
         {
-            if (curWidth >= bounds.Width)
+            if (prevTab is { })
             {
             {
+                tab.X = Pos.Right (prevTab);
+            }
+            else
+            {
+                tab.X = 0;
+            }
+
+            tab.Y = 0;
+
+            // while there is space for the tab
+            int tabTextWidth = tab.DisplayText.EnumerateRunes ().Sum (c => c.GetColumns ());
+
+            string text = tab.DisplayText;
+
+            // The maximum number of characters to use for the tab name as specified
+            // by the user (MaxTabTextWidth).  But not more than the width of the view
+            // or we won't even be able to render a single tab!
+            long maxWidth = Math.Max (0, Math.Min (bounds.Width - 3, MaxTabTextWidth));
+
+            prevTab = tab;
+
+            tab.Width = 2;
+            tab.Height = Style.ShowTopLine ? 3 : 2;
+
+            // if tab view is width <= 3 don't render any tabs
+            if (maxWidth == 0)
+            {
+                tab.Visible = true;
+                tab.MouseClick += Tab_MouseClick!;
+
+                yield return new TabToRender (tab, string.Empty, Equals (SelectedTab, tab));
+
                 break;
                 break;
             }
             }
 
 
-            if (curWidth + tabs [i].Frame.Width < bounds.Width)
+            if (tabTextWidth > maxWidth)
+            {
+                text = tab.DisplayText.Substring (0, (int)maxWidth);
+                tabTextWidth = (int)maxWidth;
+            }
+
+            tab.Width = Math.Max (tabTextWidth + 2, 1);
+            tab.Height = Style.ShowTopLine ? 3 : 2;
+
+            // if there is not enough space for this tab
+            if (i + tabTextWidth >= bounds.Width)
             {
             {
-                yield return i;
+                tab.Visible = false;
+
+                break;
             }
             }
-            curWidth += tabs [i].Frame.Width;
+
+            // there is enough space!
+            tab.Visible = true;
+            tab.MouseClick += Tab_MouseClick!;
+
+            yield return new TabToRender (tab, text, Equals (SelectedTab, tab));
+
+            i += tabTextWidth + 1;
         }
         }
     }
     }
 
 
@@ -564,214 +535,867 @@ public class TabView : View, IDesignable
         return Style.ShowTopLine ? 3 : 2;
         return Style.ShowTopLine ? 3 : 2;
     }
     }
 
 
-    /// <inheritdoc />
-    protected override void Dispose (bool disposing)
+    private void Tab_MouseClick (object sender, MouseEventArgs e)
     {
     {
-        if (disposing)
+        e.Handled = _tabsBar.NewMouseEvent (e) == true;
+    }
+
+    private void UnSetCurrentTabs ()
+    {
+        if (_tabLocations is { })
         {
         {
-            // Get once to avoid multiple enumerations
-            Tab [] tabs = _tabRowView.Tabs.ToArray ();
-            if (SelectedTabIndex is { })
+            foreach (TabToRender tabToRender in _tabLocations)
             {
             {
-                Remove (tabs [SelectedTabIndex.Value].View);
-            }
-            foreach (Tab tab in tabs)
-            {
-                tab.View?.Dispose ();
-                tab.View = null;
+                tabToRender.Tab.MouseClick -= Tab_MouseClick!;
+                tabToRender.Tab.Visible = false;
             }
             }
-        };
-        base.Dispose (disposing);
+
+            _tabLocations = null;
+        }
     }
     }
 
 
+    /// <summary>Raises the <see cref="TabClicked"/> event.</summary>
+    /// <param name="tabMouseEventArgs"></param>
+    private protected virtual void OnTabClicked (TabMouseEventArgs tabMouseEventArgs) { TabClicked?.Invoke (this, tabMouseEventArgs); }
+
     private class TabRowView : View
     private class TabRowView : View
     {
     {
+        private readonly TabView _host;
         private readonly View _leftScrollIndicator;
         private readonly View _leftScrollIndicator;
         private readonly View _rightScrollIndicator;
         private readonly View _rightScrollIndicator;
 
 
-        public TabRowView ()
+        public TabRowView (TabView host)
         {
         {
+            _host = host;
             Id = "tabRowView";
             Id = "tabRowView";
 
 
             CanFocus = true;
             CanFocus = true;
-            Height = Dim.Auto ();
+            Height = 1; // BUGBUG: Views should avoid setting Height as doing so implies Frame.Size == GetContentSize ().
             Width = Dim.Fill ();
             Width = Dim.Fill ();
-            SuperViewRendersLineCanvas = true;
 
 
             _rightScrollIndicator = new View
             _rightScrollIndicator = new View
             {
             {
                 Id = "rightScrollIndicator",
                 Id = "rightScrollIndicator",
-                X = Pos.Func (() => Viewport.X + Viewport.Width - 1),
-                Y = Pos.AnchorEnd (),
                 Width = 1,
                 Width = 1,
                 Height = 1,
                 Height = 1,
-                Visible = true,
+                Visible = false,
                 Text = Glyphs.RightArrow.ToString ()
                 Text = Glyphs.RightArrow.ToString ()
             };
             };
+            _rightScrollIndicator.MouseClick += _host.Tab_MouseClick!;
 
 
             _leftScrollIndicator = new View
             _leftScrollIndicator = new View
             {
             {
                 Id = "leftScrollIndicator",
                 Id = "leftScrollIndicator",
-                X = Pos.Func (() => Viewport.X),
-                Y = Pos.AnchorEnd (),
                 Width = 1,
                 Width = 1,
                 Height = 1,
                 Height = 1,
-                Visible = true,
+                Visible = false,
                 Text = Glyphs.LeftArrow.ToString ()
                 Text = Glyphs.LeftArrow.ToString ()
             };
             };
+            _leftScrollIndicator.MouseClick += _host.Tab_MouseClick!;
 
 
             Add (_rightScrollIndicator, _leftScrollIndicator);
             Add (_rightScrollIndicator, _leftScrollIndicator);
-
-            Initialized += OnInitialized;
         }
         }
 
 
-        private void OnInitialized (object? sender, EventArgs e)
+        protected override bool OnMouseEvent (MouseEventArgs me)
         {
         {
-            if (SuperView is TabView tabView)
+            Tab? hit = me.View as Tab;
+
+            if (me.IsSingleClicked)
             {
             {
-                _leftScrollIndicator.MouseClick += (o, args) =>
-                                                   {
-                                                       tabView.InvokeCommand (Command.ScrollLeft);
-                                                   };
-                _rightScrollIndicator.MouseClick += (o, args) =>
-                                                    {
-                                                        tabView.InvokeCommand (Command.ScrollRight);
-                                                    };
-                tabView.SelectedTabChanged += TabView_SelectedTabChanged;
+                _host.OnTabClicked (new TabMouseEventArgs (hit, me));
+
+                // user canceled click
+                if (me.Handled)
+                {
+                    return true;
+                }
             }
             }
 
 
-            CalcContentSize ();
-        }
+            if (!me.IsSingleDoubleOrTripleClicked)
+            {
+                return false;
+            }
 
 
-        private void TabView_SelectedTabChanged (object? sender, TabChangedEventArgs e)
-        {
-            _selectedTabIndex = e.NewTabIndex;
-            CalcContentSize ();
-        }
+            if (!HasFocus && CanFocus)
+            {
+                SetFocus ();
+            }
 
 
-        /// <inheritdoc />
-        public override void OnAdded (SuperViewChangedEventArgs e)
-        {
-            if (e.SubView is Tab tab)
+            if (me.IsSingleDoubleOrTripleClicked)
             {
             {
-                MoveSubviewToEnd (_leftScrollIndicator);
-                MoveSubviewToEnd (_rightScrollIndicator);
+                var scrollIndicatorHit = 0;
 
 
-                tab.HasFocusChanged += TabOnHasFocusChanged;
-                tab.Selecting += Tab_Selecting;
+                if (me.View is { } && me.View.Id == "rightScrollIndicator")
+                {
+                    scrollIndicatorHit = 1;
+                }
+                else if (me.View is { } && me.View.Id == "leftScrollIndicator")
+                {
+                    scrollIndicatorHit = -1;
+                }
+
+                if (scrollIndicatorHit != 0)
+                {
+                    _host.SwitchTabBy (scrollIndicatorHit);
+
+                    SetNeedsLayout ();
+
+                    return true;
+                }
+
+                if (hit is { })
+                {
+                    _host.SelectedTab = hit;
+                    SetNeedsLayout ();
+
+                    return true;
+                }
             }
             }
-            CalcContentSize ();
+
+            return false;
         }
         }
 
 
-        private void Tab_Selecting (object? sender, CommandEventArgs e)
+        /// <inheritdoc />
+        protected override bool OnClearingViewport ()
         {
         {
-            e.Cancel = RaiseSelecting (new CommandContext (Command.Select, null, data: Tabs.ToArray ().IndexOf (sender))) is true;
+            // clear any old text
+            ClearViewport ();
+
+            return true;
         }
         }
 
 
-        private void TabOnHasFocusChanged (object? sender, HasFocusEventArgs e)
+        protected override bool OnDrawingContent ()
         {
         {
-            TabView? host = SuperView as TabView;
+            _host._tabLocations = _host.CalculateViewport (Viewport).ToArray ();
 
 
-            if (host is null)
-            {
-                return;
-            }
 
 
+            SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ());
 
 
-            //if (e is { NewFocused: Tab tab, NewValue: true })
-            //{
-            //    e.Cancel = RaiseSInvokeCommand (Command.Select, new CommandContext () { Data = tab }) is true;
-            //}
+            return true;
         }
         }
 
 
-        public void CalcContentSize ()
+        /// <inheritdoc />
+        protected override bool OnDrawingSubviews ()
         {
         {
-            TabView? host = SuperView as TabView;
+            RenderTabLine ();
 
 
-            if (host is null)
+            return true;
+        }
+
+        protected override void OnDrawComplete ()
+        {
+            if (_host._tabLocations is null)
             {
             {
                 return;
                 return;
             }
             }
 
 
-            Tab? selected = null;
-            int topLine = host!.Style.ShowTopLine ? 1 : 0;
-
-            Tab [] tabs = Tabs.ToArray ();
+            TabToRender [] tabLocations = _host._tabLocations;
+            int selectedTab = -1;
 
 
-            for (int i = 0; i < tabs.Length; i++)
+            for (var i = 0; i < tabLocations.Length; i++)
             {
             {
-                tabs [i].Height = Dim.Fill ();
-                if (i == 0)
+                View tab = tabLocations [i].Tab;
+                Rectangle vts = tab.ViewportToScreen (tab.Viewport);
+                var lc = new LineCanvas ();
+                int selectedOffset = _host.Style.ShowTopLine && tabLocations [i].IsSelected ? 0 : 1;
+
+                if (tabLocations [i].IsSelected)
+                {
+                    selectedTab = i;
+
+                    if (i == 0 && _host.TabScrollOffset == 0)
+                    {
+                        if (_host.Style.TabsOnBottom)
+                        {
+                            // Upper left vertical line
+                            lc.AddLine (
+                                        new Point (vts.X - 1, vts.Y - 1),
+                                        -1,
+                                        Orientation.Vertical,
+                                        tab.BorderStyle
+                                       );
+                        }
+                        else
+                        {
+                            // Lower left vertical line
+                            lc.AddLine (
+                                        new Point (vts.X - 1, vts.Bottom - selectedOffset),
+                                        -1,
+                                        Orientation.Vertical,
+                                        tab.BorderStyle
+                                       );
+                        }
+                    }
+                    else if (i > 0 && i <= tabLocations.Length - 1)
+                    {
+                        if (_host.Style.TabsOnBottom)
+                        {
+                            // URCorner
+                            lc.AddLine (
+                                        new Point (vts.X - 1, vts.Y - 1),
+                                        1,
+                                        Orientation.Vertical,
+                                        tab.BorderStyle
+                                       );
+
+                            lc.AddLine (
+                                        new Point (vts.X - 1, vts.Y - 1),
+                                        -1,
+                                        Orientation.Horizontal,
+                                        tab.BorderStyle
+                                       );
+                        }
+                        else
+                        {
+                            // LRCorner
+                            lc.AddLine (
+                                        new Point (vts.X - 1, vts.Bottom - selectedOffset),
+                                        -1,
+                                        Orientation.Vertical,
+                                        tab.BorderStyle
+                                       );
+
+                            lc.AddLine (
+                                        new Point (vts.X - 1, vts.Bottom - selectedOffset),
+                                        -1,
+                                        Orientation.Horizontal,
+                                        tab.BorderStyle
+                                       );
+                        }
+
+                        if (_host.Style.ShowTopLine)
+                        {
+                            if (_host.Style.TabsOnBottom)
+                            {
+                                // Lower left tee
+                                lc.AddLine (
+                                            new Point (vts.X - 1, vts.Bottom),
+                                            -1,
+                                            Orientation.Vertical,
+                                            tab.BorderStyle
+                                           );
+
+                                lc.AddLine (
+                                            new Point (vts.X - 1, vts.Bottom),
+                                            0,
+                                            Orientation.Horizontal,
+                                            tab.BorderStyle
+                                           );
+                            }
+                            else
+                            {
+                                // Upper left tee
+                                lc.AddLine (
+                                            new Point (vts.X - 1, vts.Y - 1),
+                                            1,
+                                            Orientation.Vertical,
+                                            tab.BorderStyle
+                                           );
+
+                                lc.AddLine (
+                                            new Point (vts.X - 1, vts.Y - 1),
+                                            0,
+                                            Orientation.Horizontal,
+                                            tab.BorderStyle
+                                           );
+                            }
+                        }
+                    }
+
+                    if (i < tabLocations.Length - 1)
+                    {
+                        if (_host.Style.ShowTopLine)
+                        {
+                            if (_host.Style.TabsOnBottom)
+                            {
+                                // Lower right tee
+                                lc.AddLine (
+                                            new Point (vts.Right, vts.Bottom),
+                                            -1,
+                                            Orientation.Vertical,
+                                            tab.BorderStyle
+                                           );
+
+                                lc.AddLine (
+                                            new Point (vts.Right, vts.Bottom),
+                                            0,
+                                            Orientation.Horizontal,
+                                            tab.BorderStyle
+                                           );
+                            }
+                            else
+                            {
+                                // Upper right tee
+                                lc.AddLine (
+                                            new Point (vts.Right, vts.Y - 1),
+                                            1,
+                                            Orientation.Vertical,
+                                            tab.BorderStyle
+                                           );
+
+                                lc.AddLine (
+                                            new Point (vts.Right, vts.Y - 1),
+                                            0,
+                                            Orientation.Horizontal,
+                                            tab.BorderStyle
+                                           );
+                            }
+                        }
+                    }
+
+                    if (_host.Style.TabsOnBottom)
+                    {
+                        //URCorner
+                        lc.AddLine (
+                                    new Point (vts.Right, vts.Y - 1),
+                                    1,
+                                    Orientation.Vertical,
+                                    tab.BorderStyle
+                                   );
+
+                        lc.AddLine (
+                                    new Point (vts.Right, vts.Y - 1),
+                                    1,
+                                    Orientation.Horizontal,
+                                    tab.BorderStyle
+                                   );
+                    }
+                    else
+                    {
+                        //LLCorner
+                        lc.AddLine (
+                                    new Point (vts.Right, vts.Bottom - selectedOffset),
+                                    -1,
+                                    Orientation.Vertical,
+                                    tab.BorderStyle
+                                   );
+
+                        lc.AddLine (
+                                    new Point (vts.Right, vts.Bottom - selectedOffset),
+                                    1,
+                                    Orientation.Horizontal,
+                                    tab.BorderStyle
+                                   );
+                    }
+                }
+                else if (selectedTab == -1)
                 {
                 {
-                    tabs [i].X = 0;
+                    if (i == 0 && string.IsNullOrEmpty (tab.Text))
+                    {
+                        if (_host.Style.TabsOnBottom)
+                        {
+                            if (_host.Style.ShowTopLine)
+                            {
+                                // LLCorner
+                                lc.AddLine (
+                                            new Point (vts.X - 1, vts.Bottom),
+                                            -1,
+                                            Orientation.Vertical,
+                                            tab.BorderStyle
+                                           );
+
+                                lc.AddLine (
+                                            new Point (vts.X - 1, vts.Bottom),
+                                            1,
+                                            Orientation.Horizontal,
+                                            tab.BorderStyle
+                                           );
+                            }
+
+                            // ULCorner
+                            lc.AddLine (
+                                        new Point (vts.X - 1, vts.Y - 1),
+                                        1,
+                                        Orientation.Vertical,
+                                        tab.BorderStyle
+                                       );
+
+                            lc.AddLine (
+                                        new Point (vts.X - 1, vts.Y - 1),
+                                        1,
+                                        Orientation.Horizontal,
+                                        tab.BorderStyle
+                                       );
+                        }
+                        else
+                        {
+                            if (_host.Style.ShowTopLine)
+                            {
+                                // ULCorner
+                                lc.AddLine (
+                                            new Point (vts.X - 1, vts.Y - 1),
+                                            1,
+                                            Orientation.Vertical,
+                                            tab.BorderStyle
+                                           );
+
+                                lc.AddLine (
+                                            new Point (vts.X - 1, vts.Y - 1),
+                                            1,
+                                            Orientation.Horizontal,
+                                            tab.BorderStyle
+                                           );
+                            }
+
+                            // LLCorner
+                            lc.AddLine (
+                                        new Point (vts.X - 1, vts.Bottom),
+                                        -1,
+                                        Orientation.Vertical,
+                                        tab.BorderStyle
+                                       );
+
+                            lc.AddLine (
+                                        new Point (vts.X - 1, vts.Bottom),
+                                        1,
+                                        Orientation.Horizontal,
+                                        tab.BorderStyle
+                                       );
+                        }
+                    }
+                    else if (i > 0)
+                    {
+                        if (_host.Style.ShowTopLine || _host.Style.TabsOnBottom)
+                        {
+                            // Upper left tee
+                            lc.AddLine (
+                                        new Point (vts.X - 1, vts.Y - 1),
+                                        1,
+                                        Orientation.Vertical,
+                                        tab.BorderStyle
+                                       );
+
+                            lc.AddLine (
+                                        new Point (vts.X - 1, vts.Y - 1),
+                                        0,
+                                        Orientation.Horizontal,
+                                        tab.BorderStyle
+                                       );
+                        }
+
+                        // Lower left tee
+                        lc.AddLine (
+                                    new Point (vts.X - 1, vts.Bottom),
+                                    -1,
+                                    Orientation.Vertical,
+                                    tab.BorderStyle
+                                   );
+
+                        lc.AddLine (
+                                    new Point (vts.X - 1, vts.Bottom),
+                                    0,
+                                    Orientation.Horizontal,
+                                    tab.BorderStyle
+                                   );
+                    }
                 }
                 }
-                else
+                else if (i < tabLocations.Length - 1)
+                {
+                    if (_host.Style.ShowTopLine)
+                    {
+                        // Upper right tee
+                        lc.AddLine (
+                                    new Point (vts.Right, vts.Y - 1),
+                                    1,
+                                    Orientation.Vertical,
+                                    tab.BorderStyle
+                                   );
+
+                        lc.AddLine (
+                                    new Point (vts.Right, vts.Y - 1),
+                                    0,
+                                    Orientation.Horizontal,
+                                    tab.BorderStyle
+                                   );
+                    }
+
+                    if (_host.Style.ShowTopLine || !_host.Style.TabsOnBottom)
+                    {
+                        // Lower right tee
+                        lc.AddLine (
+                                    new Point (vts.Right, vts.Bottom),
+                                    -1,
+                                    Orientation.Vertical,
+                                    tab.BorderStyle
+                                   );
+
+                        lc.AddLine (
+                                    new Point (vts.Right, vts.Bottom),
+                                    0,
+                                    Orientation.Horizontal,
+                                    tab.BorderStyle
+                                   );
+                    }
+                    else
+                    {
+                        // Upper right tee
+                        lc.AddLine (
+                                    new Point (vts.Right, vts.Y - 1),
+                                    1,
+                                    Orientation.Vertical,
+                                    tab.BorderStyle
+                                   );
+
+                        lc.AddLine (
+                                    new Point (vts.Right, vts.Y - 1),
+                                    0,
+                                    Orientation.Horizontal,
+                                    tab.BorderStyle
+                                   );
+                    }
+                }
+
+                if (i == 0 && i != selectedTab && _host.TabScrollOffset == 0 && _host.Style.ShowBorder)
+                {
+                    if (_host.Style.TabsOnBottom)
+                    {
+                        // Upper left vertical line
+                        lc.AddLine (
+                                    new Point (vts.X - 1, vts.Y - 1),
+                                    0,
+                                    Orientation.Vertical,
+                                    tab.BorderStyle
+                                   );
+
+                        lc.AddLine (
+                                    new Point (vts.X - 1, vts.Y - 1),
+                                    1,
+                                    Orientation.Horizontal,
+                                    tab.BorderStyle
+                                   );
+                    }
+                    else
+                    {
+                        // Lower left vertical line
+                        lc.AddLine (
+                                    new Point (vts.X - 1, vts.Bottom),
+                                    0,
+                                    Orientation.Vertical,
+                                    tab.BorderStyle
+                                   );
+
+                        lc.AddLine (
+                                    new Point (vts.X - 1, vts.Bottom),
+                                    1,
+                                    Orientation.Horizontal,
+                                    tab.BorderStyle
+                                   );
+                    }
+                }
+
+                if (i == tabLocations.Length - 1 && i != selectedTab)
                 {
                 {
-                    tabs [i].X = Pos.Right (tabs [i - 1]);
+                    if (_host.Style.TabsOnBottom)
+                    {
+                        // Upper right tee
+                        lc.AddLine (
+                                    new Point (vts.Right, vts.Y - 1),
+                                    1,
+                                    Orientation.Vertical,
+                                    tab.BorderStyle
+                                   );
+
+                        lc.AddLine (
+                                    new Point (vts.Right, vts.Y - 1),
+                                    0,
+                                    Orientation.Horizontal,
+                                    tab.BorderStyle
+                                   );
+                    }
+                    else
+                    {
+                        // Lower right tee
+                        lc.AddLine (
+                                    new Point (vts.Right, vts.Bottom),
+                                    -1,
+                                    Orientation.Vertical,
+                                    tab.BorderStyle
+                                   );
+
+                        lc.AddLine (
+                                    new Point (vts.Right, vts.Bottom),
+                                    0,
+                                    Orientation.Horizontal,
+                                    tab.BorderStyle
+                                   );
+                    }
                 }
                 }
 
 
-                if (i == _selectedTabIndex)
+                if (i == tabLocations.Length - 1)
                 {
                 {
-                    selected = tabs [i];
+                    var arrowOffset = 1;
 
 
-                    if (host.Style.TabsOnBottom)
+                    int lastSelectedTab = !_host.Style.ShowTopLine && i == selectedTab ? 1 :
+                                          _host.Style.TabsOnBottom ? 1 : 0;
+                    Rectangle tabsBarVts = ViewportToScreen (Viewport);
+                    int lineLength = tabsBarVts.Right - vts.Right;
+
+                    // Right horizontal line
+                    if (ShouldDrawRightScrollIndicator ())
                     {
                     {
-                        tabs [i].Border.Thickness = new Thickness (1, 0, 1, topLine);
-                        tabs [i].Margin.Thickness = new Thickness (0, 1, 0, 0);
+                        if (lineLength - arrowOffset > 0)
+                        {
+                            if (_host.Style.TabsOnBottom)
+                            {
+                                lc.AddLine (
+                                            new Point (vts.Right, vts.Y - lastSelectedTab),
+                                            lineLength - arrowOffset,
+                                            Orientation.Horizontal,
+                                            tab.BorderStyle
+                                           );
+                            }
+                            else
+                            {
+                                lc.AddLine (
+                                            new Point (
+                                                       vts.Right,
+                                                       vts.Bottom - lastSelectedTab
+                                                      ),
+                                            lineLength - arrowOffset,
+                                            Orientation.Horizontal,
+                                            tab.BorderStyle
+                                           );
+                            }
+                        }
                     }
                     }
                     else
                     else
                     {
                     {
-                        tabs [i].Border.Thickness = new Thickness (1, topLine, 1, 0);
-                        tabs [i].Margin.Thickness = new Thickness (0, 0, 0, topLine);
+                        if (_host.Style.TabsOnBottom)
+                        {
+                            lc.AddLine (
+                                        new Point (vts.Right, vts.Y - lastSelectedTab),
+                                        lineLength,
+                                        Orientation.Horizontal,
+                                        tab.BorderStyle
+                                       );
+                        }
+                        else
+                        {
+                            lc.AddLine (
+                                        new Point (vts.Right, vts.Bottom - lastSelectedTab),
+                                        lineLength,
+                                        Orientation.Horizontal,
+                                        tab.BorderStyle
+                                       );
+                        }
+
+                        if (_host.Style.ShowBorder)
+                        {
+                            if (_host.Style.TabsOnBottom)
+                            {
+                                // More LRCorner
+                                lc.AddLine (
+                                            new Point (
+                                                       tabsBarVts.Right - 1,
+                                                       vts.Y - lastSelectedTab
+                                                      ),
+                                            -1,
+                                            Orientation.Vertical,
+                                            tab.BorderStyle
+                                           );
+                            }
+                            else
+                            {
+                                // More URCorner
+                                lc.AddLine (
+                                            new Point (
+                                                       tabsBarVts.Right - 1,
+                                                       vts.Bottom - lastSelectedTab
+                                                      ),
+                                            1,
+                                            Orientation.Vertical,
+                                            tab.BorderStyle
+                                           );
+                            }
+                        }
+                    }
+                }
+
+                tab.LineCanvas.Merge (lc);
+                tab.RenderLineCanvas ();
+
+                RenderUnderline ();
+            }
+        }
+
+        private int GetUnderlineYPosition ()
+        {
+            if (_host.Style.TabsOnBottom)
+            {
+                return 0;
+            }
+
+            return _host.Style.ShowTopLine ? 2 : 1;
+        }
+
+        /// <summary>Renders the line with the tab names in it.</summary>
+        private void RenderTabLine ()
+        {
+            TabToRender []? tabLocations = _host._tabLocations;
+
+            if (tabLocations is null)
+            {
+                return;
+            }
+
+            View? selected = null;
+            int topLine = _host.Style.ShowTopLine ? 1 : 0;
+
+            foreach (TabToRender toRender in tabLocations)
+            {
+                Tab tab = toRender.Tab;
+
+                if (toRender.IsSelected)
+                {
+                    selected = tab;
+
+                    if (_host.Style.TabsOnBottom)
+                    {
+                        tab.Border.Thickness = new Thickness (1, 0, 1, topLine);
+                        tab.Margin.Thickness = new Thickness (0, 1, 0, 0);
+                    }
+                    else
+                    {
+                        tab.Border.Thickness = new Thickness (1, topLine, 1, 0);
+                        tab.Margin.Thickness = new Thickness (0, 0, 0, topLine);
                     }
                     }
                 }
                 }
                 else if (selected is null)
                 else if (selected is null)
                 {
                 {
-                    if (host.Style.TabsOnBottom)
+                    if (_host.Style.TabsOnBottom)
                     {
                     {
-                        tabs [i].Border.Thickness = new Thickness (1, 1, 0, topLine);
-                        tabs [i].Margin.Thickness = new Thickness (0, 0, 0, 0);
+                        tab.Border.Thickness = new Thickness (1, 1, 0, topLine);
+                        tab.Margin.Thickness = new Thickness (0, 0, 0, 0);
                     }
                     }
                     else
                     else
                     {
                     {
-                        tabs [i].Border.Thickness = new Thickness (1, topLine, 0, 1);
-                        tabs [i].Margin.Thickness = new Thickness (0, 0, 0, 0);
+                        tab.Border.Thickness = new Thickness (1, topLine, 0, 1);
+                        tab.Margin.Thickness = new Thickness (0, 0, 0, 0);
                     }
                     }
 
 
-                    //tabs [i].Width = Math.Max (tabs [i].Width!.GetAnchor (0) - 1, 1);
+                    tab.Width = Math.Max (tab.Width!.GetAnchor (0) - 1, 1);
                 }
                 }
                 else
                 else
                 {
                 {
-                    if (host.Style.TabsOnBottom)
+                    if (_host.Style.TabsOnBottom)
                     {
                     {
-                        tabs [i].Border.Thickness = new Thickness (0, 1, 1, topLine);
-                        tabs [i].Margin.Thickness = new Thickness (0, 0, 0, 0);
+                        tab.Border.Thickness = new Thickness (0, 1, 1, topLine);
+                        tab.Margin.Thickness = new Thickness (0, 0, 0, 0);
                     }
                     }
                     else
                     else
                     {
                     {
-                        tabs [i].Border.Thickness = new Thickness (0, topLine, 1, 1);
-                        tabs [i].Margin.Thickness = new Thickness (0, 0, 0, 0);
+                        tab.Border.Thickness = new Thickness (0, topLine, 1, 1);
+                        tab.Margin.Thickness = new Thickness (0, 0, 0, 0);
                     }
                     }
 
 
-                    //tabs [i].Width = Math.Max (tabs [i].Width!.GetAnchor (0) - 1, 1);
+                    tab.Width = Math.Max (tab.Width!.GetAnchor (0) - 1, 1);
                 }
                 }
 
 
-                //tabs [i].Text = toRender.TextToRender;
+                tab.Text = toRender.TextToRender;
+
+                // BUGBUG: Layout should only be called from Mainloop iteration!
+                Layout ();
+
+                tab.DrawBorderAndPadding ();
+
+                Attribute prevAttr = Driver?.GetAttribute () ?? Attribute.Default;
+
+                // if tab is the selected one and focus is inside this control
+                if (toRender.IsSelected && _host.HasFocus)
+                {
+                    if (_host.Focused == this)
+                    {
+                        // if focus is the tab bar itself then show that they can switch tabs
+                        prevAttr = ColorScheme.HotFocus;
+                    }
+                    else
+                    {
+                        // Focus is inside the tab
+                        prevAttr = ColorScheme.HotNormal;
+                    }
+                }
+
+                tab.TextFormatter.Draw (
+                                        tab.ViewportToScreen (tab.Viewport),
+                                        prevAttr,
+                                        ColorScheme.HotNormal
+                                       );
+
+                tab.DrawBorderAndPadding ();
+
+
+                SetAttribute (GetNormalColor ());
+            }
+        }
+
+        /// <summary>Renders the line of the tab that adjoins the content of the tab.</summary>
+        private void RenderUnderline ()
+        {
+            int y = GetUnderlineYPosition ();
+
+            TabToRender? selected = _host._tabLocations?.FirstOrDefault (t => t.IsSelected);
+
+            if (selected is null)
+            {
+                return;
             }
             }
 
 
-            SetContentSize (null);
-            Layout (Application.Screen.Size);
+            // draw scroll indicators
+
+            // if there are more tabs to the left not visible
+            if (_host.TabScrollOffset > 0)
+            {
+                _leftScrollIndicator.X = 0;
+                _leftScrollIndicator.Y = y;
+
+                // indicate that
+                _leftScrollIndicator.Visible = true;
 
 
-            var width = 0;
-            foreach (Tab t in tabs)
+                // Ensures this is clicked instead of the first tab
+                MoveSubviewToEnd (_leftScrollIndicator);
+                _leftScrollIndicator.Draw ();
+            }
+            else
             {
             {
-                width += t.Frame.Width;
+                _leftScrollIndicator.Visible = false;
             }
             }
-            SetContentSize (new (width, Viewport.Height));
+
+            // if there are more tabs to the right not visible
+            if (ShouldDrawRightScrollIndicator ())
+            {
+                _rightScrollIndicator.X = Viewport.Width - 1;
+                _rightScrollIndicator.Y = y;
+
+                // indicate that
+                _rightScrollIndicator.Visible = true;
+
+                // Ensures this is clicked instead of the last tab if under this
+                MoveSubviewToStart (_rightScrollIndicator);
+                _rightScrollIndicator.Draw ();
+            }
+            else
+            {
+                _rightScrollIndicator.Visible = false;
+            }
+        }
+
+        private bool ShouldDrawRightScrollIndicator () { return _host._tabLocations!.LastOrDefault ()?.Tab != _host.Tabs.LastOrDefault (); }
+    }
+
+    private class TabToRender
+    {
+        public TabToRender (Tab tab, string textToRender, bool isSelected)
+        {
+            Tab = tab;
+            IsSelected = isSelected;
+            TextToRender = textToRender;
         }
         }
 
 
-        internal IEnumerable<Tab> Tabs => Subviews.Where (v => v is Tab).Cast<Tab> ();
+        /// <summary>True if the tab that is being rendered is the selected one.</summary>
+        /// <value></value>
+        public bool IsSelected { get; }
 
 
-        private int? _selectedTabIndex = null;
+        public Tab Tab { get; }
+        public string TextToRender { get; }
     }
     }
 }
 }

+ 3 - 3
UICatalog/Scenarios/Editor.cs

@@ -743,8 +743,8 @@ public class Editor : Scenario
         _findReplaceWindow.Visible = true;
         _findReplaceWindow.Visible = true;
         _findReplaceWindow.SuperView.MoveSubviewToStart (_findReplaceWindow);
         _findReplaceWindow.SuperView.MoveSubviewToStart (_findReplaceWindow);
         _tabView.SetFocus ();
         _tabView.SetFocus ();
-        _tabView.SelectedTabIndex = isFind ? 0 : 1;
-      //  _tabView.SelectedTabIndex.View.FocusDeepest (NavigationDirection.Forward, null);
+        _tabView.SelectedTab = isFind ? _tabView.Tabs.ToArray () [0] : _tabView.Tabs.ToArray () [1];
+        _tabView.SelectedTab.View.FocusDeepest (NavigationDirection.Forward, null);
     }
     }
 
 
     private void CreateFindReplace ()
     private void CreateFindReplace ()
@@ -758,7 +758,7 @@ public class Editor : Scenario
 
 
         _tabView.AddTab (new () { DisplayText = "Find", View = CreateFindTab () }, true);
         _tabView.AddTab (new () { DisplayText = "Find", View = CreateFindTab () }, true);
         _tabView.AddTab (new () { DisplayText = "Replace", View = CreateReplaceTab () }, false);
         _tabView.AddTab (new () { DisplayText = "Replace", View = CreateReplaceTab () }, false);
-        //_tabView.SelectedTabChanged += (s, e) => _tabView.SelectedTabIndex.View.FocusDeepest (NavigationDirection.Forward, null);
+        _tabView.SelectedTabChanged += (s, e) => _tabView.SelectedTab.View.FocusDeepest (NavigationDirection.Forward, null);
         _findReplaceWindow.Add (_tabView);
         _findReplaceWindow.Add (_tabView);
 
 
 //        _tabView.SelectedTab.View.FocusLast (null); // Hack to get the first tab to be focused
 //        _tabView.SelectedTab.View.FocusLast (null); // Hack to get the first tab to be focused

+ 5 - 5
UICatalog/Scenarios/Images.cs

@@ -293,11 +293,11 @@ public class Images : Scenario
 
 
     private void ApplyShowTabViewHack ()
     private void ApplyShowTabViewHack ()
     {
     {
-        //// TODO HACK: This hack seems to be required to make tabview actually refresh itself
-        //_tabView.SetNeedsDraw ();
-        //var orig = _tabView.SelectedTabIndex;
-        //_tabView.SelectedTabIndex = _tabView.Tabs.Except (new [] { orig }).ElementAt (0);
-        //_tabView.SelectedTabIndex = orig;
+        // TODO HACK: This hack seems to be required to make tabview actually refresh itself
+        _tabView.SetNeedsDraw ();
+        var orig = _tabView.SelectedTab;
+        _tabView.SelectedTab = _tabView.Tabs.Except (new [] { orig }).ElementAt (0);
+        _tabView.SelectedTab = orig;
     }
     }
 
 
     private void BuildBasicTab (Tab tabBasic)
     private void BuildBasicTab (Tab tabBasic)

+ 6 - 7
UICatalog/Scenarios/Notepad.cs

@@ -98,7 +98,7 @@ public class Notepad : Scenario
         Application.Shutdown ();
         Application.Shutdown ();
     }
     }
 
 
-    public void Save () { Save (_focusedTabView, _focusedTabView.Tabs.ToArray () [_focusedTabView.SelectedTabIndex!.Value]); }
+    public void Save () { Save (_focusedTabView, _focusedTabView.SelectedTab); }
 
 
     public void Save (TabView tabViewToSave, Tab tabToSave)
     public void Save (TabView tabViewToSave, Tab tabToSave)
     {
     {
@@ -120,7 +120,7 @@ public class Notepad : Scenario
 
 
     public bool SaveAs ()
     public bool SaveAs ()
     {
     {
-        var tab = _focusedTabView.Tabs.ToArray () [_focusedTabView.SelectedTabIndex!.Value] as OpenedFile;
+        var tab = _focusedTabView.SelectedTab as OpenedFile;
 
 
         if (tab == null)
         if (tab == null)
         {
         {
@@ -153,7 +153,7 @@ public class Notepad : Scenario
         return true;
         return true;
     }
     }
 
 
-    private void Close () { Close (_focusedTabView, _focusedTabView.Tabs.ToArray()[_focusedTabView.SelectedTabIndex!.Value]); }
+    private void Close () { Close (_focusedTabView, _focusedTabView.SelectedTab); }
 
 
     private void Close (TabView tv, Tab tabToClose)
     private void Close (TabView tv, Tab tabToClose)
     {
     {
@@ -239,7 +239,7 @@ public class Notepad : Scenario
     {
     {
         var tv = new TabView { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill () };
         var tv = new TabView { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill () };
 
 
-       // tv.TabClicked += TabView_TabClicked;
+        tv.TabClicked += TabView_TabClicked;
         tv.SelectedTabChanged += TabView_SelectedTabChanged;
         tv.SelectedTabChanged += TabView_SelectedTabChanged;
         tv.HasFocusChanging += (s, e) => _focusedTabView = tv;
         tv.HasFocusChanging += (s, e) => _focusedTabView = tv;
 
 
@@ -320,10 +320,9 @@ public class Notepad : Scenario
 
 
     private void TabView_SelectedTabChanged (object sender, TabChangedEventArgs e)
     private void TabView_SelectedTabChanged (object sender, TabChangedEventArgs e)
     {
     {
-        Tab tab = _focusedTabView.Tabs.ToArray () [e.NewTabIndex!.Value];
-        LenShortcut.Title = $"Len:{tab?.View?.Text?.Length ?? 0}";
+        LenShortcut.Title = $"Len:{e.NewTab?.View?.Text?.Length ?? 0}";
 
 
-        tab?.View?.SetFocus ();
+        e.NewTab?.View?.SetFocus ();
     }
     }
 
 
     private void TabView_TabClicked (object sender, TabMouseEventArgs e)
     private void TabView_TabClicked (object sender, TabMouseEventArgs e)

+ 2 - 2
UICatalog/Scenarios/TabViewExample.cs

@@ -35,7 +35,7 @@ public class TabViewExample : Scenario
                          new (
                          new (
                               "_Clear SelectedTab",
                               "_Clear SelectedTab",
                               "",
                               "",
-                              () => _tabView.SelectedTabIndex = null
+                              () => _tabView.SelectedTab = null
                              ),
                              ),
                          new ("_Quit", "", Quit)
                          new ("_Quit", "", Quit)
                      }
                      }
@@ -129,7 +129,7 @@ public class TabViewExample : Scenario
                             );
                             );
         }
         }
 
 
-        _tabView.SelectedTabIndex = 0;
+        _tabView.SelectedTab = _tabView.Tabs.First ();
 
 
         appWindow.Add (_tabView);
         appWindow.Add (_tabView);