diff --git a/Terminal.Gui/Views/TabView.cs b/Terminal.Gui/Views/TabView.cs
deleted file mode 100644
index 99d90bec28..0000000000
--- a/Terminal.Gui/Views/TabView.cs
+++ /dev/null
@@ -1,1404 +0,0 @@
-#nullable enable
-namespace Terminal.Gui;
-
-/// Control that hosts multiple sub views, presenting a single one at once.
-public class TabView : View
-{
- /// The default to set on new controls.
- public const uint DefaultMaxTabTextWidth = 30;
-
- ///
- /// This sub view is the main client area of the current tab. It hosts the of the tab, the
- /// .
- ///
- private readonly View _contentView;
-
- private readonly List _tabs = new ();
-
- /// This sub view is the 2 or 3 line control that represents the actual tabs themselves.
- private readonly TabRowView _tabsBar;
-
- private Tab? _selectedTab;
- private TabToRender []? _tabLocations;
- private int _tabScrollOffset;
-
- /// Initializes a class.
- public TabView ()
- {
- CanFocus = true;
- TabStop = TabBehavior.TabStop; // Because TabView has focusable subviews, it must be a TabGroup
- _tabsBar = new TabRowView (this);
- _contentView = new View ()
- {
- //Id = "TabView._contentView",
- };
- ApplyStyleChanges ();
-
- base.Add (_tabsBar);
- base.Add (_contentView);
-
- // Things this view knows how to do
- AddCommand (Command.Left, () => SwitchTabBy (-1));
-
- AddCommand (Command.Right, () => SwitchTabBy (1));
-
- AddCommand (
- Command.LeftStart,
- () =>
- {
- TabScrollOffset = 0;
- SelectedTab = Tabs.FirstOrDefault ()!;
-
- return true;
- }
- );
-
- AddCommand (
- Command.RightEnd,
- () =>
- {
- TabScrollOffset = Tabs.Count - 1;
- SelectedTab = Tabs.LastOrDefault ()!;
-
- return true;
- }
- );
-
- AddCommand (
- Command.PageDown,
- () =>
- {
- TabScrollOffset += _tabLocations!.Length;
- SelectedTab = Tabs.ElementAt (TabScrollOffset);
-
- return true;
- }
- );
-
- AddCommand (
- Command.PageUp,
- () =>
- {
- TabScrollOffset -= _tabLocations!.Length;
- SelectedTab = Tabs.ElementAt (TabScrollOffset);
-
- return true;
- }
- );
-
- // Default keybindings for this view
- KeyBindings.Add (Key.CursorLeft, Command.Left);
- KeyBindings.Add (Key.CursorRight, Command.Right);
- KeyBindings.Add (Key.Home, Command.LeftStart);
- KeyBindings.Add (Key.End, Command.RightEnd);
- KeyBindings.Add (Key.PageDown, Command.PageDown);
- KeyBindings.Add (Key.PageUp, Command.PageUp);
- }
-
- ///
- /// The maximum number of characters to render in a Tab header. This prevents one long tab from pushing out all
- /// the others.
- ///
- public uint MaxTabTextWidth { get; set; } = DefaultMaxTabTextWidth;
-
- /// The currently selected member of chosen by the user.
- ///
- public Tab? SelectedTab
- {
- get => _selectedTab;
- set
- {
- UnSetCurrentTabs ();
-
- Tab? old = _selectedTab;
-
- if (_selectedTab is { })
- {
- if (_selectedTab.View is { })
- {
- _selectedTab.View.CanFocusChanged -= ContentViewCanFocus!;
- // remove old content
- _contentView.Remove (_selectedTab.View);
- }
- }
-
- _selectedTab = value;
-
- // add new content
- if (_selectedTab?.View != null)
- {
- _selectedTab.View.CanFocusChanged += ContentViewCanFocus!;
- _contentView.Add (_selectedTab.View);
- // _contentView.Id = $"_contentView for {_selectedTab.DisplayText}";
- }
-
- ContentViewCanFocus (null!, null!);
-
- EnsureSelectedTabIsVisible ();
-
- if (old != _selectedTab)
- {
- if (old?.HasFocus == true)
- {
- SelectedTab?.SetFocus ();
- }
-
- OnSelectedTabChanged (old!, _selectedTab!);
- }
- SetNeedsLayout ();
- }
- }
-
- private void ContentViewCanFocus (object sender, EventArgs eventArgs)
- {
- _contentView.CanFocus = _contentView.Subviews.Count (v => v.CanFocus) > 0;
- }
-
- private TabStyle _style = new ();
-
- /// Render choices for how to display tabs. After making changes, call .
- ///
- public TabStyle Style
- {
- get => _style;
- set
- {
- if (_style == value)
- {
- return;
- }
- _style = value;
- SetNeedsLayout ();
- }
- }
-
- /// All tabs currently hosted by the control.
- ///
- public IReadOnlyCollection Tabs => _tabs.AsReadOnly ();
-
- /// When there are too many tabs to render, this indicates the first tab to render on the screen.
- ///
- public int TabScrollOffset
- {
- get => _tabScrollOffset;
- set
- {
- _tabScrollOffset = EnsureValidScrollOffsets (value);
- SetNeedsLayout ();
- }
- }
-
- /// Adds the given to .
- ///
- /// True to make the newly added Tab the .
- public void AddTab (Tab tab, bool andSelect)
- {
- if (_tabs.Contains (tab))
- {
- return;
- }
-
- _tabs.Add (tab);
- _tabsBar.Add (tab);
-
- if (SelectedTab is null || andSelect)
- {
- SelectedTab = tab;
-
- EnsureSelectedTabIsVisible ();
-
- tab.View?.SetFocus ();
- }
-
- SetNeedsLayout ();
- }
-
- ///
- /// Updates the control to use the latest state settings in . 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
- /// .
- ///
- public void ApplyStyleChanges ()
- {
- _contentView.BorderStyle = Style.ShowBorder ? LineStyle.Single : LineStyle.None;
- _contentView.Width = Dim.Fill ();
-
- if (Style.TabsOnBottom)
- {
- // Tabs are along the bottom so just dodge the border
- if (Style.ShowBorder)
- {
- _contentView.Border.Thickness = new Thickness (1, 1, 1, 0);
- }
-
- _contentView.Y = 0;
-
- int tabHeight = GetTabHeight (false);
-
- // Fill client area leaving space at bottom for tabs
- _contentView.Height = Dim.Fill (tabHeight);
-
- _tabsBar.Height = tabHeight;
-
- _tabsBar.Y = Pos.Bottom (_contentView);
- }
- else
- {
- // Tabs are along the top
- if (Style.ShowBorder)
- {
- _contentView.Border.Thickness = new Thickness (1, 0, 1, 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
- }
-
- SetNeedsLayout ();
- }
-
- /// Updates to ensure that is visible.
- public void EnsureSelectedTabIsVisible ()
- {
- if (!IsInitialized || SelectedTab is null)
- {
- return;
- }
-
- // if current viewport does not include the selected tab
- if (!CalculateViewport (Viewport).Any (r => Equals (SelectedTab, r.Tab)))
- {
- // Set scroll offset so the first tab rendered is the
- TabScrollOffset = Math.Max (0, Tabs.IndexOf (SelectedTab));
- }
- }
-
- /// Updates to be a valid index of .
- /// The value to validate.
- /// Changes will not be immediately visible in the display until you call .
- /// The valid for the given value.
- public int EnsureValidScrollOffsets (int value) { return Math.Max (Math.Min (value, Tabs.Count - 1), 0); }
-
- ///
- protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedView)
- {
- if (SelectedTab is { } && !_contentView.CanFocus && focusedView == this)
- {
- SelectedTab?.SetFocus ();
-
- return;
- }
-
- base.OnHasFocusChanged (newHasFocus, previousFocusedView, focusedView);
- }
-
- ///
- protected override bool OnDrawingContent ()
- {
- if (Tabs.Any ())
- {
- // Region savedClip = SetClip ();
- _tabsBar.Draw ();
- _contentView.SetNeedsDraw ();
- _contentView.Draw ();
-
- //if (Driver is { })
- //{
- // Driver.Clip = savedClip;
- //}
- }
-
- return true;
- }
-
- ///
- /// Removes the given from . Caller is responsible for disposing the
- /// tab's hosted if appropriate.
- ///
- ///
- public void RemoveTab (Tab? tab)
- {
- if (tab is null || !_tabs.Contains (tab))
- {
- return;
- }
-
- // what tab was selected before closing
- int idx = _tabs.IndexOf (tab);
-
- _tabs.Remove (tab);
-
- // if the currently selected tab is no longer a member of Tabs
- if (SelectedTab is null || !Tabs.Contains (SelectedTab))
- {
- // 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 ();
- }
-
- /// Event for when changes.
- public event EventHandler? SelectedTabChanged;
-
- ///
- /// Changes the by the given . Positive for right, negative for
- /// left. If no tab is currently selected then the first tab will become selected.
- ///
- ///
- public bool SwitchTabBy (int amount)
- {
- if (Tabs.Count == 0)
- {
- return false;
- }
-
- // if there is only one tab anyway or nothing is selected
- if (Tabs.Count == 1 || SelectedTab is null)
- {
- SelectedTab = Tabs.ElementAt (0);
-
- return SelectedTab is { };
- }
-
- int currentIdx = Tabs.IndexOf (SelectedTab);
-
- // Currently selected tab has vanished!
- if (currentIdx == -1)
- {
- SelectedTab = Tabs.ElementAt (0);
- return true;
- }
-
- int newIdx = Math.Max (0, Math.Min (currentIdx + amount, Tabs.Count - 1));
-
- if (newIdx == currentIdx)
- {
- return false;
- }
-
- SelectedTab = _tabs [newIdx];
-
- EnsureSelectedTabIsVisible ();
-
- return true;
- }
-
- ///
- /// Event fired when a is clicked. Can be used to cancel navigation, show context menu (e.g. on
- /// right click) etc.
- ///
- public event EventHandler? TabClicked;
-
- /// Disposes the control and all .
- ///
- 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 ();
- }
- }
- }
-
- /// Raises the event.
- protected virtual void OnSelectedTabChanged (Tab oldTab, Tab newTab)
- {
- SelectedTabChanged?.Invoke (this, new TabChangedEventArgs (oldTab, newTab));
- }
-
- /// Returns which tabs to render at each x location.
- ///
- private IEnumerable CalculateViewport (Rectangle bounds)
- {
- UnSetCurrentTabs ();
-
- var i = 1;
- View? prevTab = null;
-
- // Starting at the first or scrolled to tab
- foreach (Tab tab in Tabs.Skip (TabScrollOffset))
- {
- 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;
- }
-
- 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)
- {
- tab.Visible = false;
-
- break;
- }
-
- // there is enough space!
- tab.Visible = true;
- tab.MouseClick += Tab_MouseClick!;
-
- yield return new TabToRender (tab, text, Equals (SelectedTab, tab));
-
- i += tabTextWidth + 1;
- }
- }
-
- ///
- /// Returns the number of rows occupied by rendering the tabs, this depends on
- /// and can be 0 (e.g. if and you ask for ).
- ///
- /// True to measure the space required at the top of the control, false to measure space at the bottom.
- /// .
- ///
- private int GetTabHeight (bool top)
- {
- if (top && Style.TabsOnBottom)
- {
- return 0;
- }
-
- if (!top && !Style.TabsOnBottom)
- {
- return 0;
- }
-
- return Style.ShowTopLine ? 3 : 2;
- }
-
- private void Tab_MouseClick (object sender, MouseEventArgs e)
- {
- e.Handled = _tabsBar.NewMouseEvent (e) == true;
- }
-
- private void UnSetCurrentTabs ()
- {
- if (_tabLocations is { })
- {
- foreach (TabToRender tabToRender in _tabLocations)
- {
- tabToRender.Tab.MouseClick -= Tab_MouseClick!;
- tabToRender.Tab.Visible = false;
- }
-
- _tabLocations = null;
- }
- }
-
- /// Raises the event.
- ///
- private protected virtual void OnTabClicked (TabMouseEventArgs tabMouseEventArgs) { TabClicked?.Invoke (this, tabMouseEventArgs); }
-
- private class TabRowView : View
- {
- private readonly TabView _host;
- private readonly View _leftScrollIndicator;
- private readonly View _rightScrollIndicator;
-
- public TabRowView (TabView host)
- {
- _host = host;
- Id = "tabRowView";
-
- CanFocus = true;
- Height = 1; // BUGBUG: Views should avoid setting Height as doing so implies Frame.Size == GetContentSize ().
- Width = Dim.Fill ();
-
- _rightScrollIndicator = new View
- {
- Id = "rightScrollIndicator",
- Width = 1,
- Height = 1,
- Visible = false,
- Text = Glyphs.RightArrow.ToString ()
- };
- _rightScrollIndicator.MouseClick += _host.Tab_MouseClick!;
-
- _leftScrollIndicator = new View
- {
- Id = "leftScrollIndicator",
- Width = 1,
- Height = 1,
- Visible = false,
- Text = Glyphs.LeftArrow.ToString ()
- };
- _leftScrollIndicator.MouseClick += _host.Tab_MouseClick!;
-
- Add (_rightScrollIndicator, _leftScrollIndicator);
- }
-
- protected override bool OnMouseEvent (MouseEventArgs me)
- {
- Tab? hit = me.View as Tab;
-
- if (me.IsSingleClicked)
- {
- _host.OnTabClicked (new TabMouseEventArgs (hit, me));
-
- // user canceled click
- if (me.Handled)
- {
- return true;
- }
- }
-
- if (!me.IsSingleDoubleOrTripleClicked)
- {
- return false;
- }
-
- if (!HasFocus && CanFocus)
- {
- SetFocus ();
- }
-
- if (me.IsSingleDoubleOrTripleClicked)
- {
- var scrollIndicatorHit = 0;
-
- 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;
- }
- }
-
- return false;
- }
-
- ///
- protected override bool OnClearingViewport ()
- {
- // clear any old text
- ClearViewport ();
-
- return true;
- }
-
- protected override bool OnDrawingContent ()
- {
- _host._tabLocations = _host.CalculateViewport (Viewport).ToArray ();
-
- RenderTabLine ();
-
- RenderUnderline ();
-
- SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ());
-
- return true;
- }
-
- ///
- protected override bool OnDrawingSubviews ()
- {
- // RenderTabLine ();
-
- return false;
- }
-
- protected override void OnDrawComplete ()
- {
- if (_host._tabLocations is null)
- {
- return;
- }
-
- TabToRender [] tabLocations = _host._tabLocations;
- int selectedTab = -1;
-
- for (var i = 0; i < tabLocations.Length; i++)
- {
- 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)
- {
- 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 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)
- {
- 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 == tabLocations.Length - 1)
- {
- var arrowOffset = 1;
-
- 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 ())
- {
- 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
- {
- 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;
- }
-
- /// Renders the line with the tab names in it.
- 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)
- {
- if (_host.Style.TabsOnBottom)
- {
- tab.Border.Thickness = new Thickness (1, 1, 0, topLine);
- tab.Margin.Thickness = new Thickness (0, 0, 0, 0);
- }
- else
- {
- tab.Border.Thickness = new Thickness (1, topLine, 0, 1);
- tab.Margin.Thickness = new Thickness (0, 0, 0, 0);
- }
-
- tab.Width = Math.Max (tab.Width!.GetAnchor (0) - 1, 1);
- }
- else
- {
- if (_host.Style.TabsOnBottom)
- {
- tab.Border.Thickness = new Thickness (0, 1, 1, topLine);
- tab.Margin.Thickness = new Thickness (0, 0, 0, 0);
- }
- else
- {
- tab.Border.Thickness = new Thickness (0, topLine, 1, 1);
- tab.Margin.Thickness = new Thickness (0, 0, 0, 0);
- }
-
- tab.Width = Math.Max (tab.Width!.GetAnchor (0) - 1, 1);
- }
-
- 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 ());
- }
- }
-
- /// Renders the line of the tab that adjoins the content of the tab.
- private void RenderUnderline ()
- {
- int y = GetUnderlineYPosition ();
-
- TabToRender? selected = _host._tabLocations?.FirstOrDefault (t => t.IsSelected);
-
- if (selected is null)
- {
- return;
- }
-
- // 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;
-
- // Ensures this is clicked instead of the first tab
- MoveSubviewToEnd (_leftScrollIndicator);
- _leftScrollIndicator.Draw ();
- }
- else
- {
- _leftScrollIndicator.Visible = false;
- }
-
- // 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;
- }
-
- /// True if the tab that is being rendered is the selected one.
- ///
- public bool IsSelected { get; }
-
- public Tab Tab { get; }
- public string TextToRender { get; }
- }
-}
diff --git a/Terminal.Gui/Views/Tab.cs b/Terminal.Gui/Views/TabView/Tab.cs
similarity index 96%
rename from Terminal.Gui/Views/Tab.cs
rename to Terminal.Gui/Views/TabView/Tab.cs
index b683b04b6f..52fb0bdf9d 100644
--- a/Terminal.Gui/Views/Tab.cs
+++ b/Terminal.Gui/Views/TabView/Tab.cs
@@ -22,7 +22,7 @@ public string DisplayText
set
{
_displayText = value;
- SetNeedsDraw ();
+ SetNeedsLayout ();
}
}
diff --git a/Terminal.Gui/Views/TabChangedEventArgs.cs b/Terminal.Gui/Views/TabView/TabChangedEventArgs.cs
similarity index 100%
rename from Terminal.Gui/Views/TabChangedEventArgs.cs
rename to Terminal.Gui/Views/TabView/TabChangedEventArgs.cs
diff --git a/Terminal.Gui/Views/TabMouseEventArgs.cs b/Terminal.Gui/Views/TabView/TabMouseEventArgs.cs
similarity index 100%
rename from Terminal.Gui/Views/TabMouseEventArgs.cs
rename to Terminal.Gui/Views/TabView/TabMouseEventArgs.cs
diff --git a/Terminal.Gui/Views/TabView/TabRowView.cs b/Terminal.Gui/Views/TabView/TabRowView.cs
new file mode 100644
index 0000000000..b1ec596ecc
--- /dev/null
+++ b/Terminal.Gui/Views/TabView/TabRowView.cs
@@ -0,0 +1,793 @@
+#nullable enable
+namespace Terminal.Gui;
+
+internal class TabRowView : View
+{
+ private readonly TabView _host;
+ private readonly View _leftScrollIndicator;
+ private readonly View _rightScrollIndicator;
+
+ public TabRowView (TabView host)
+ {
+ _host = host;
+ Id = "tabRowView";
+
+ CanFocus = true;
+ Width = Dim.Fill ();
+
+ _rightScrollIndicator = new View
+ {
+ Id = "rightScrollIndicator",
+ Width = 1,
+ Height = 1,
+ Visible = false,
+ Text = Glyphs.RightArrow.ToString ()
+ };
+ _rightScrollIndicator.MouseClick += _host.Tab_MouseClick!;
+
+ _leftScrollIndicator = new View
+ {
+ Id = "leftScrollIndicator",
+ Width = 1,
+ Height = 1,
+ Visible = false,
+ Text = Glyphs.LeftArrow.ToString ()
+ };
+ _leftScrollIndicator.MouseClick += _host.Tab_MouseClick!;
+
+ Add (_rightScrollIndicator, _leftScrollIndicator);
+ }
+
+ protected override bool OnMouseEvent (MouseEventArgs me)
+ {
+ View? parent = me.View is Adornment adornment ? adornment.Parent : me.View;
+ Tab? hit = parent as Tab;
+
+ if (me.IsSingleClicked)
+ {
+ _host.OnTabClicked (new TabMouseEventArgs (hit!, me));
+
+ // user canceled click
+ if (me.Handled)
+ {
+ return true;
+ }
+
+ if (parent == _host.SelectedTab)
+ {
+ _host.SelectedTab?.SetFocus ();
+ }
+ }
+
+ if (!me.IsSingleDoubleOrTripleClicked)
+ {
+ return false;
+ }
+
+ if (!HasFocus && CanFocus)
+ {
+ SetFocus ();
+ }
+
+ if (me.IsSingleDoubleOrTripleClicked)
+ {
+ var scrollIndicatorHit = 0;
+
+ if (me.View is { Id: "rightScrollIndicator" })
+ {
+ scrollIndicatorHit = 1;
+ }
+ else if (me.View is { Id: "leftScrollIndicator" })
+ {
+ scrollIndicatorHit = -1;
+ }
+
+ if (scrollIndicatorHit != 0)
+ {
+ _host.SwitchTabBy (scrollIndicatorHit);
+
+ return true;
+ }
+
+ if (hit is { })
+ {
+ _host.SelectedTab = hit;
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedView)
+ {
+ if (_host.SelectedTab is { HasFocus: false, CanFocus: true } && focusedView == this)
+ {
+ _host.SelectedTab?.SetFocus ();
+
+ return;
+ }
+
+ base.OnHasFocusChanged (newHasFocus, previousFocusedView, focusedView);
+ }
+
+ ///
+ protected override void OnSubviewLayout (LayoutEventArgs args)
+ {
+ _host._tabLocations = _host.CalculateViewport (Viewport).ToArray ();
+
+ RenderTabLine ();
+
+ RenderUnderline ();
+
+ base.OnSubviewLayout (args);
+ }
+
+ ///
+ protected override bool OnRenderingLineCanvas ()
+ {
+ RenderTabLineCanvas ();
+
+ return false;
+ }
+
+ private void RenderTabLineCanvas ()
+ {
+ if (_host._tabLocations is null)
+ {
+ return;
+ }
+
+ Tab [] tabLocations = _host._tabLocations;
+ int selectedTab = -1;
+ var lc = new LineCanvas ();
+
+ for (var i = 0; i < tabLocations.Length; i++)
+ {
+ View tab = tabLocations [i];
+ Rectangle vts = tab.ViewportToScreen (tab.Viewport);
+ int selectedOffset = _host.Style.ShowTopLine && tabLocations [i] == _host.SelectedTab ? 0 : 1;
+
+ if (tabLocations [i] == _host.SelectedTab)
+ {
+ 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)
+ {
+ 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 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 is { TabScrollOffset: 0, Style.ShowBorder: true })
+ {
+ 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)
+ {
+ 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 == tabLocations.Length - 1)
+ {
+ var arrowOffset = 1;
+
+ 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 ())
+ {
+ 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
+ {
+ // Right corner
+ 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
+ );
+ }
+ }
+ }
+ }
+ }
+
+ _host.LineCanvas.Merge (lc);
+ }
+
+ private int GetUnderlineYPosition ()
+ {
+ if (_host.Style.TabsOnBottom)
+ {
+ return 0;
+ }
+
+ return _host.Style.ShowTopLine ? 2 : 1;
+ }
+
+ /// Renders the line with the tab names in it.
+ private void RenderTabLine ()
+ {
+ if (_host._tabLocations is null)
+ {
+ return;
+ }
+
+ View? selected = null;
+ int topLine = _host.Style.ShowTopLine ? 1 : 0;
+
+ foreach (Tab toRender in _host._tabLocations)
+ {
+ Tab tab = toRender;
+
+ if (toRender == _host.SelectedTab)
+ {
+ selected = tab;
+
+ if (_host.Style.TabsOnBottom)
+ {
+ tab.Border!.Thickness = new (1, 0, 1, topLine);
+ tab.Margin!.Thickness = new (0, 1, 0, 0);
+ }
+ else
+ {
+ tab.Border!.Thickness = new (1, topLine, 1, 0);
+ tab.Margin!.Thickness = new (0, 0, 0, topLine);
+ }
+ }
+ else if (selected is null)
+ {
+ if (_host.Style.TabsOnBottom)
+ {
+ tab.Border!.Thickness = new (1, 1, 1, topLine);
+ tab.Margin!.Thickness = new (0, 0, 0, 0);
+ }
+ else
+ {
+ tab.Border!.Thickness = new (1, topLine, 1, 1);
+ tab.Margin!.Thickness = new (0, 0, 0, 0);
+ }
+ }
+ else
+ {
+ if (_host.Style.TabsOnBottom)
+ {
+ tab.Border!.Thickness = new (1, 1, 1, topLine);
+ tab.Margin!.Thickness = new (0, 0, 0, 0);
+ }
+ else
+ {
+ tab.Border!.Thickness = new (1, topLine, 1, 1);
+ tab.Margin!.Thickness = new (0, 0, 0, 0);
+ }
+ }
+
+ // Ensures updating TextFormatter constrains
+ tab.TextFormatter.ConstrainToWidth = tab.GetContentSize ().Width;
+ tab.TextFormatter.ConstrainToHeight = tab.GetContentSize ().Height;
+ }
+ }
+
+ /// Renders the line of the tab that adjoins the content of the tab.
+ private void RenderUnderline ()
+ {
+ int y = GetUnderlineYPosition ();
+
+ Tab? selected = _host._tabLocations?.FirstOrDefault (t => t == _host.SelectedTab);
+
+ if (selected is null)
+ {
+ return;
+ }
+
+ // 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;
+
+ // Ensures this is clicked instead of the first tab
+ MoveSubviewToEnd (_leftScrollIndicator);
+ }
+ else
+ {
+ _leftScrollIndicator.Visible = false;
+ }
+
+ // 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);
+ }
+ else
+ {
+ _rightScrollIndicator.Visible = false;
+ }
+ }
+
+ private bool ShouldDrawRightScrollIndicator () { return _host._tabLocations!.LastOrDefault () != _host.Tabs.LastOrDefault (); }
+}
diff --git a/Terminal.Gui/Views/TabStyle.cs b/Terminal.Gui/Views/TabView/TabStyle.cs
similarity index 100%
rename from Terminal.Gui/Views/TabStyle.cs
rename to Terminal.Gui/Views/TabView/TabStyle.cs
diff --git a/Terminal.Gui/Views/TabView/TabView.cs b/Terminal.Gui/Views/TabView/TabView.cs
new file mode 100644
index 0000000000..7916911fcd
--- /dev/null
+++ b/Terminal.Gui/Views/TabView/TabView.cs
@@ -0,0 +1,585 @@
+#nullable enable
+namespace Terminal.Gui;
+
+/// Control that hosts multiple sub views, presenting a single one at once.
+public class TabView : View
+{
+ /// The default to set on new controls.
+ public const uint DefaultMaxTabTextWidth = 30;
+
+ ///
+ /// This sub view is the main client area of the current tab. It hosts the of the tab, the
+ /// .
+ ///
+ private readonly View _containerView;
+
+ private readonly List _tabs = new ();
+
+ /// This sub view is the 2 or 3 line control that represents the actual tabs themselves.
+ private readonly TabRowView _tabsBar;
+
+ private Tab? _selectedTab;
+
+ internal Tab []? _tabLocations;
+ private int _tabScrollOffset;
+
+ /// Initializes a class.
+ public TabView ()
+ {
+ CanFocus = true;
+ TabStop = TabBehavior.TabStop; // Because TabView has focusable subviews, it must be a TabGroup
+ _tabsBar = new TabRowView (this);
+ _containerView = new ();
+ ApplyStyleChanges ();
+
+ base.Add (_tabsBar);
+ base.Add (_containerView);
+
+ // Things this view knows how to do
+ AddCommand (Command.Left, () => SwitchTabBy (-1));
+
+ AddCommand (Command.Right, () => SwitchTabBy (1));
+
+ AddCommand (
+ Command.LeftStart,
+ () =>
+ {
+ TabScrollOffset = 0;
+ SelectedTab = Tabs.FirstOrDefault ()!;
+
+ return true;
+ }
+ );
+
+ AddCommand (
+ Command.RightEnd,
+ () =>
+ {
+ TabScrollOffset = Tabs.Count - 1;
+ SelectedTab = Tabs.LastOrDefault ()!;
+
+ return true;
+ }
+ );
+
+ AddCommand (
+ Command.PageDown,
+ () =>
+ {
+ TabScrollOffset += _tabLocations!.Length;
+ SelectedTab = Tabs.ElementAt (TabScrollOffset);
+
+ return true;
+ }
+ );
+
+ AddCommand (
+ Command.PageUp,
+ () =>
+ {
+ TabScrollOffset -= _tabLocations!.Length;
+ SelectedTab = Tabs.ElementAt (TabScrollOffset);
+
+ return true;
+ }
+ );
+
+ // Default keybindings for this view
+ KeyBindings.Add (Key.CursorLeft, Command.Left);
+ KeyBindings.Add (Key.CursorRight, Command.Right);
+ KeyBindings.Add (Key.Home, Command.LeftStart);
+ KeyBindings.Add (Key.End, Command.RightEnd);
+ KeyBindings.Add (Key.PageDown, Command.PageDown);
+ KeyBindings.Add (Key.PageUp, Command.PageUp);
+ }
+
+ ///
+ /// The maximum number of characters to render in a Tab header. This prevents one long tab from pushing out all
+ /// the others.
+ ///
+ public uint MaxTabTextWidth { get; set; } = DefaultMaxTabTextWidth;
+
+ // This is needed to hold initial value because it may change during the setter process
+ private bool _selectedTabHasFocus;
+
+ /// The currently selected member of chosen by the user.
+ ///
+ public Tab? SelectedTab
+ {
+ get => _selectedTab;
+ set
+ {
+ if (value == _selectedTab)
+ {
+ return;
+ }
+
+ Tab? old = _selectedTab;
+ _selectedTabHasFocus = old is { } && (old.HasFocus || !_containerView.CanFocus);
+
+ if (_selectedTab is { })
+ {
+ if (_selectedTab.View is { })
+ {
+ _selectedTab.View.CanFocusChanged -= ContainerViewCanFocus!;
+ // remove old content
+ _containerView.Remove (_selectedTab.View);
+ }
+ }
+
+ _selectedTab = value;
+
+ // add new content
+ if (_selectedTab?.View != null)
+ {
+ _selectedTab.View.CanFocusChanged += ContainerViewCanFocus!;
+ _containerView.Add (_selectedTab.View);
+ }
+
+ ContainerViewCanFocus (null!, null!);
+
+ EnsureSelectedTabIsVisible ();
+
+ if (old != _selectedTab)
+ {
+ if (TabCanSetFocus ())
+ {
+ SelectedTab?.SetFocus ();
+ }
+
+ OnSelectedTabChanged (old!, _selectedTab!);
+ }
+ SetNeedsLayout ();
+ }
+ }
+
+ private bool TabCanSetFocus ()
+ {
+ return IsInitialized && SelectedTab is { } && (_selectedTabHasFocus || !_containerView.CanFocus);
+ }
+
+ private void ContainerViewCanFocus (object sender, EventArgs eventArgs)
+ {
+ _containerView.CanFocus = _containerView.Subviews.Count (v => v.CanFocus) > 0;
+ }
+
+ private TabStyle _style = new ();
+
+ /// Render choices for how to display tabs. After making changes, call .
+ ///
+ public TabStyle Style
+ {
+ get => _style;
+ set
+ {
+ if (_style == value)
+ {
+ return;
+ }
+ _style = value;
+ SetNeedsLayout ();
+ }
+ }
+
+ /// All tabs currently hosted by the control.
+ ///
+ public IReadOnlyCollection Tabs => _tabs.AsReadOnly ();
+
+ /// When there are too many tabs to render, this indicates the first tab to render on the screen.
+ ///
+ public int TabScrollOffset
+ {
+ get => _tabScrollOffset;
+ set
+ {
+ _tabScrollOffset = EnsureValidScrollOffsets (value);
+ SetNeedsLayout ();
+ }
+ }
+
+ /// Adds the given to .
+ ///
+ /// True to make the newly added Tab the .
+ public void AddTab (Tab tab, bool andSelect)
+ {
+ if (_tabs.Contains (tab))
+ {
+ return;
+ }
+
+ _tabs.Add (tab);
+ _tabsBar.Add (tab);
+
+ if (SelectedTab is null || andSelect)
+ {
+ SelectedTab = tab;
+
+ EnsureSelectedTabIsVisible ();
+
+ tab.View?.SetFocus ();
+ }
+
+ SetNeedsLayout ();
+ }
+
+ ///
+ /// Updates the control to use the latest state settings in . 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
+ /// .
+ ///
+ public void ApplyStyleChanges ()
+ {
+ _containerView.BorderStyle = Style.ShowBorder ? LineStyle.Single : LineStyle.None;
+ _containerView.Width = Dim.Fill ();
+
+ if (Style.TabsOnBottom)
+ {
+ // Tabs are along the bottom so just dodge the border
+ if (Style.ShowBorder)
+ {
+ _containerView.Border!.Thickness = new Thickness (1, 1, 1, 0);
+ }
+
+ _containerView.Y = 0;
+
+ int tabHeight = GetTabHeight (false);
+
+ // Fill client area leaving space at bottom for tabs
+ _containerView.Height = Dim.Fill (tabHeight);
+
+ _tabsBar.Height = tabHeight;
+
+ _tabsBar.Y = Pos.Bottom (_containerView);
+ }
+ else
+ {
+ // Tabs are along the top
+ if (Style.ShowBorder)
+ {
+ _containerView.Border!.Thickness = new Thickness (1, 0, 1, 1);
+ }
+
+ _tabsBar.Y = 0;
+
+ int tabHeight = GetTabHeight (true);
+
+ //move content down to make space for tabs
+ _containerView.Y = Pos.Bottom (_tabsBar);
+
+ // Fill client area leaving space at bottom for border
+ _containerView.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
+ }
+
+ SetNeedsLayout ();
+ }
+
+ ///
+ protected override void OnViewportChanged (DrawEventArgs e)
+ {
+ _tabLocations = CalculateViewport (Viewport).ToArray ();
+
+ base.OnViewportChanged (e);
+ }
+
+ /// Updates to ensure that is visible.
+ public void EnsureSelectedTabIsVisible ()
+ {
+ if (!IsInitialized || SelectedTab is null)
+ {
+ return;
+ }
+
+ // if current viewport does not include the selected tab
+ if (!CalculateViewport (Viewport).Any (t => Equals (SelectedTab, t)))
+ {
+ // Set scroll offset so the first tab rendered is the
+ TabScrollOffset = Math.Max (0, Tabs.IndexOf (SelectedTab));
+ }
+ }
+
+ /// Updates to be a valid index of .
+ /// The value to validate.
+ /// Changes will not be immediately visible in the display until you call .
+ /// The valid for the given value.
+ public int EnsureValidScrollOffsets (int value) { return Math.Max (Math.Min (value, Tabs.Count - 1), 0); }
+
+ ///
+ protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedView)
+ {
+ if (SelectedTab is { HasFocus: false } && !_containerView.CanFocus && focusedView == this)
+ {
+ SelectedTab?.SetFocus ();
+
+ return;
+ }
+
+ base.OnHasFocusChanged (newHasFocus, previousFocusedView, focusedView);
+ }
+
+ ///
+ /// Removes the given from . Caller is responsible for disposing the
+ /// tab's hosted if appropriate.
+ ///
+ ///
+ public void RemoveTab (Tab? tab)
+ {
+ if (tab is null || !_tabs.Contains (tab))
+ {
+ return;
+ }
+
+ // what tab was selected before closing
+ int idx = _tabs.IndexOf (tab);
+
+ _tabs.Remove (tab);
+
+ // if the currently selected tab is no longer a member of Tabs
+ if (SelectedTab is null || !Tabs.Contains (SelectedTab))
+ {
+ // 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 ();
+ }
+
+ /// Event for when changes.
+ public event EventHandler? SelectedTabChanged;
+
+ ///
+ /// Changes the by the given . Positive for right, negative for
+ /// left. If no tab is currently selected then the first tab will become selected.
+ ///
+ ///
+ public bool SwitchTabBy (int amount)
+ {
+ if (Tabs.Count == 0)
+ {
+ return false;
+ }
+
+ // if there is only one tab anyway or nothing is selected
+ if (Tabs.Count == 1 || SelectedTab is null)
+ {
+ SelectedTab = Tabs.ElementAt (0);
+
+ return SelectedTab is { };
+ }
+
+ int currentIdx = Tabs.IndexOf (SelectedTab);
+
+ // Currently selected tab has vanished!
+ if (currentIdx == -1)
+ {
+ SelectedTab = Tabs.ElementAt (0);
+ return true;
+ }
+
+ int newIdx = Math.Max (0, Math.Min (currentIdx + amount, Tabs.Count - 1));
+
+ if (newIdx == currentIdx)
+ {
+ return false;
+ }
+
+ SelectedTab = _tabs [newIdx];
+
+ EnsureSelectedTabIsVisible ();
+
+ return true;
+ }
+
+ ///
+ /// Event fired when a is clicked. Can be used to cancel navigation, show context menu (e.g. on
+ /// right click) etc.
+ ///
+ public event EventHandler? TabClicked;
+
+ /// Disposes the control and all .
+ ///
+ 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 ();
+ }
+ }
+ }
+
+ /// Raises the event.
+ protected virtual void OnSelectedTabChanged (Tab oldTab, Tab newTab)
+ {
+ SelectedTabChanged?.Invoke (this, new TabChangedEventArgs (oldTab, newTab));
+ }
+
+ /// Returns which tabs to render at each x location.
+ ///
+ internal IEnumerable CalculateViewport (Rectangle bounds)
+ {
+ UnSetCurrentTabs ();
+
+ var i = 1;
+ View? prevTab = null;
+
+ // Starting at the first or scrolled to tab
+ foreach (Tab tab in Tabs.Skip (TabScrollOffset))
+ {
+ if (prevTab is { })
+ {
+ tab.X = Pos.Right (prevTab) - 1;
+ }
+ else
+ {
+ tab.X = 0;
+ }
+
+ tab.Y = 0;
+
+ // while there is space for the tab
+ int tabTextWidth = tab.DisplayText.EnumerateRunes ().Sum (c => c.GetColumns ());
+
+ // 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));
+
+ 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!;
+ tab.Border!.MouseClick += Tab_MouseClick!;
+
+ yield return tab;
+
+ break;
+ }
+
+ if (tabTextWidth > maxWidth)
+ {
+ tab.Text = tab.DisplayText.Substring (0, (int)maxWidth);
+ tabTextWidth = (int)maxWidth;
+ }
+ else
+ {
+ tab.Text = tab.DisplayText;
+ }
+
+ 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)
+ {
+ tab.Visible = false;
+
+ break;
+ }
+
+ // there is enough space!
+ tab.Visible = true;
+ tab.MouseClick += Tab_MouseClick!;
+ tab.Border!.MouseClick += Tab_MouseClick!;
+
+ yield return tab;
+
+ prevTab = tab;
+
+ i += tabTextWidth + 1;
+ }
+
+ if (TabCanSetFocus ())
+ {
+ SelectedTab?.SetFocus ();
+ }
+ }
+
+ ///
+ /// Returns the number of rows occupied by rendering the tabs, this depends on
+ /// and can be 0 (e.g. if and you ask for ).
+ ///
+ /// True to measure the space required at the top of the control, false to measure space at the bottom.
+ /// .
+ ///
+ private int GetTabHeight (bool top)
+ {
+ if (top && Style.TabsOnBottom)
+ {
+ return 0;
+ }
+
+ if (!top && !Style.TabsOnBottom)
+ {
+ return 0;
+ }
+
+ return Style.ShowTopLine ? 3 : 2;
+ }
+
+ internal void Tab_MouseClick (object sender, MouseEventArgs e)
+ {
+ e.Handled = _tabsBar.NewMouseEvent (e) == true;
+ }
+
+ private void UnSetCurrentTabs ()
+ {
+ if (_tabLocations is null)
+ {
+ // Ensures unset any visible tab prior to TabScrollOffset
+ for (int i = 0; i < TabScrollOffset; i++)
+ {
+ Tab tab = Tabs.ElementAt (i);
+
+ if (tab.Visible)
+ {
+ tab.MouseClick -= Tab_MouseClick!;
+ tab.Border!.MouseClick -= Tab_MouseClick!;
+ tab.Visible = false;
+ }
+ }
+ }
+ else if (_tabLocations is { })
+ {
+ foreach (Tab tabToRender in _tabLocations)
+ {
+ tabToRender.MouseClick -= Tab_MouseClick!;
+ tabToRender.Border!.MouseClick -= Tab_MouseClick!;
+ tabToRender.Visible = false;
+ }
+
+ _tabLocations = null;
+ }
+ }
+
+ /// Raises the event.
+ ///
+ internal virtual void OnTabClicked (TabMouseEventArgs tabMouseEventArgs) { TabClicked?.Invoke (this, tabMouseEventArgs); }
+
+
+}
\ No newline at end of file
diff --git a/UnitTests/TestHelpers.cs b/UnitTests/TestHelpers.cs
index 1b841ee65a..b8d3bb98da 100644
--- a/UnitTests/TestHelpers.cs
+++ b/UnitTests/TestHelpers.cs
@@ -197,6 +197,11 @@ public override void After (MethodInfo methodUnderTest)
// Turn off diagnostic flags in case some test left them on
View.Diagnostics = ViewDiagnosticFlags.Off;
+ if (Application.Driver is { })
+ {
+ ((FakeDriver)Application.Driver).End ();
+ }
+
Application.Driver = null;
base.After (methodUnderTest);
}
diff --git a/UnitTests/View/Layout/SetLayoutTests.cs b/UnitTests/View/Layout/SetLayoutTests.cs
index 4859957d62..d348fe2b10 100644
--- a/UnitTests/View/Layout/SetLayoutTests.cs
+++ b/UnitTests/View/Layout/SetLayoutTests.cs
@@ -812,6 +812,4 @@ public void Does_Not_Throw_If_Nested_SubViews_Ref_Topmost_SuperView ()
Assert.Equal (19, v2.Frame.Height);
t.Dispose ();
}
-
-
}
diff --git a/UnitTests/Views/TabViewTests.cs b/UnitTests/Views/TabViewTests.cs
index 3999142fae..1ab880b7f9 100644
--- a/UnitTests/Views/TabViewTests.cs
+++ b/UnitTests/Views/TabViewTests.cs
@@ -3,7 +3,6 @@
namespace Terminal.Gui.ViewsTests;
-#if foo
public class TabViewTests (ITestOutputHelper output)
{
[Fact]
@@ -113,8 +112,6 @@ public void MouseClick_ChangesTab ()
tv.Width = 20;
tv.Height = 5;
- tv.Layout ();
-
tv.Draw ();
View tabRow = tv.Subviews [0];
@@ -146,21 +143,21 @@ public void MouseClick_ChangesTab ()
{
args = new () { ScreenPosition = new (i, 1), Flags = MouseFlags.ReportMousePosition };
Application.RaiseMouseEvent (args);
- Application.LayoutAndDrawToplevels ();
+ Application.LayoutAndDraw ();
Assert.Null (clicked);
Assert.Equal (tab1, tv.SelectedTab);
}
args = new () { ScreenPosition = new (3, 1), Flags = MouseFlags.Button1Clicked };
Application.RaiseMouseEvent (args);
- Application.LayoutAndDrawToplevels ();
+ Application.LayoutAndDraw ();
Assert.Equal (tab1, clicked);
Assert.Equal (tab1, tv.SelectedTab);
// Click to tab2
args = new () { ScreenPosition = new (6, 1), Flags = MouseFlags.Button1Clicked };
Application.RaiseMouseEvent (args);
- Application.LayoutAndDrawToplevels ();
+ Application.LayoutAndDraw ();
Assert.Equal (tab2, clicked);
Assert.Equal (tab2, tv.SelectedTab);
@@ -173,7 +170,7 @@ public void MouseClick_ChangesTab ()
args = new () { ScreenPosition = new (3, 1), Flags = MouseFlags.Button1Clicked };
Application.RaiseMouseEvent (args);
- Application.LayoutAndDrawToplevels ();
+ Application.LayoutAndDraw ();
// Tab 1 was clicked but event handler blocked navigation
Assert.Equal (tab1, clicked);
@@ -181,7 +178,7 @@ public void MouseClick_ChangesTab ()
args = new () { ScreenPosition = new (12, 1), Flags = MouseFlags.Button1Clicked };
Application.RaiseMouseEvent (args);
- Application.LayoutAndDrawToplevels ();
+ Application.LayoutAndDraw ();
// Clicking beyond last tab should raise event with null Tab
Assert.Null (clicked);
@@ -198,8 +195,6 @@ public void MouseClick_Right_Left_Arrows_ChangesTab ()
tv.Width = 7;
tv.Height = 5;
- tv.LayoutSubviews ();
-
tv.Draw ();
View tabRow = tv.Subviews [0];
@@ -236,7 +231,7 @@ public void MouseClick_Right_Left_Arrows_ChangesTab ()
// Click the right arrow
var args = new MouseEventArgs { ScreenPosition = new (6, 2), Flags = MouseFlags.Button1Clicked };
Application.RaiseMouseEvent (args);
- Application.LayoutAndDrawToplevels ();
+ Application.LayoutAndDraw ();
Assert.Null (clicked);
Assert.Equal (tab1, oldChanged);
Assert.Equal (tab2, newChanged);
@@ -256,7 +251,7 @@ public void MouseClick_Right_Left_Arrows_ChangesTab ()
// Click the left arrow
args = new () { ScreenPosition = new (0, 2), Flags = MouseFlags.Button1Clicked };
Application.RaiseMouseEvent (args);
- Application.LayoutAndDrawToplevels ();
+ Application.LayoutAndDraw ();
Assert.Null (clicked);
Assert.Equal (tab2, oldChanged);
Assert.Equal (tab1, newChanged);
@@ -286,8 +281,7 @@ public void MouseClick_Right_Left_Arrows_ChangesTab_With_Border ()
Assert.Equal (LineStyle.None, tv.BorderStyle);
tv.BorderStyle = LineStyle.Single;
-
- tv.LayoutSubviews ();
+ tv.Layout ();
tv.Draw ();
@@ -327,7 +321,7 @@ public void MouseClick_Right_Left_Arrows_ChangesTab_With_Border ()
// Click the right arrow
var args = new MouseEventArgs { ScreenPosition = new (7, 3), Flags = MouseFlags.Button1Clicked };
Application.RaiseMouseEvent (args);
- Application.LayoutAndDrawToplevels ();
+ Application.LayoutAndDraw ();
Assert.Null (clicked);
Assert.Equal (tab1, oldChanged);
Assert.Equal (tab2, newChanged);
@@ -349,7 +343,7 @@ public void MouseClick_Right_Left_Arrows_ChangesTab_With_Border ()
// Click the left arrow
args = new () { ScreenPosition = new (1, 3), Flags = MouseFlags.Button1Clicked };
Application.RaiseMouseEvent (args);
- Application.LayoutAndDrawToplevels ();
+ Application.LayoutAndDraw ();
Assert.Null (clicked);
Assert.Equal (tab2, oldChanged);
Assert.Equal (tab1, newChanged);
@@ -400,7 +394,7 @@ public void ProcessKey_Down_Up_Right_Left_Home_End_PageDown_PageUp ()
// Press the cursor up key to focus the selected tab
Application.RaiseKeyDownEvent (Key.CursorUp);
- Application.LayoutAndDrawToplevels ();
+ Application.LayoutAndDraw ();
// Is the selected tab focused
Assert.Equal (tab1, tv.SelectedTab);
@@ -418,7 +412,7 @@ public void ProcessKey_Down_Up_Right_Left_Home_End_PageDown_PageUp ()
// Press the cursor right key to select the next tab
Application.RaiseKeyDownEvent (Key.CursorRight);
- Application.LayoutAndDrawToplevels ();
+ Application.LayoutAndDraw ();
Assert.Equal (tab1, oldChanged);
Assert.Equal (tab2, newChanged);
Assert.Equal (tab2, tv.SelectedTab);
@@ -476,7 +470,7 @@ public void ProcessKey_Down_Up_Right_Left_Home_End_PageDown_PageUp ()
// Press the cursor left key to select the previous tab
Application.RaiseKeyDownEvent (Key.CursorLeft);
- Application.LayoutAndDrawToplevels ();
+ Application.LayoutAndDraw ();
Assert.Equal (tab2, oldChanged);
Assert.Equal (tab1, newChanged);
Assert.Equal (tab1, tv.SelectedTab);
@@ -486,7 +480,7 @@ public void ProcessKey_Down_Up_Right_Left_Home_End_PageDown_PageUp ()
// Press the end key to select the last tab
Application.RaiseKeyDownEvent (Key.End);
- Application.LayoutAndDrawToplevels ();
+ Application.LayoutAndDraw ();
Assert.Equal (tab1, oldChanged);
Assert.Equal (tab2, newChanged);
Assert.Equal (tab2, tv.SelectedTab);
@@ -495,7 +489,7 @@ public void ProcessKey_Down_Up_Right_Left_Home_End_PageDown_PageUp ()
// Press the home key to select the first tab
Application.RaiseKeyDownEvent (Key.Home);
- Application.LayoutAndDrawToplevels ();
+ Application.LayoutAndDraw ();
Assert.Equal (tab2, oldChanged);
Assert.Equal (tab1, newChanged);
Assert.Equal (tab1, tv.SelectedTab);
@@ -504,7 +498,7 @@ public void ProcessKey_Down_Up_Right_Left_Home_End_PageDown_PageUp ()
// Press the page down key to select the next set of tabs
Application.RaiseKeyDownEvent (Key.PageDown);
- Application.LayoutAndDrawToplevels ();
+ Application.LayoutAndDraw ();
Assert.Equal (tab1, oldChanged);
Assert.Equal (tab2, newChanged);
Assert.Equal (tab2, tv.SelectedTab);
@@ -513,7 +507,7 @@ public void ProcessKey_Down_Up_Right_Left_Home_End_PageDown_PageUp ()
// Press the page up key to select the previous set of tabs
Application.RaiseKeyDownEvent (Key.PageUp);
- Application.LayoutAndDrawToplevels ();
+ Application.LayoutAndDraw ();
Assert.Equal (tab2, oldChanged);
Assert.Equal (tab1, newChanged);
Assert.Equal (tab1, tv.SelectedTab);
@@ -610,7 +604,6 @@ public void ShowTopLine_False_TabsOnBottom_False_TestTabView_Width3 ()
tv.ApplyStyleChanges ();
tv.Layout ();
- View.ClipToScreen ();
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@@ -633,7 +626,7 @@ public void ShowTopLine_False_TabsOnBottom_False_TestTabView_Width4 ()
tv.Height = 5;
tv.Style = new () { ShowTopLine = false };
tv.ApplyStyleChanges ();
- tv.LayoutSubviews ();
+ tv.Layout ();
tv.Draw ();
@@ -658,13 +651,13 @@ public void ShowTopLine_False_TabsOnBottom_False_TestThinTabView_WithLongNames (
tv.Style = new () { ShowTopLine = false };
tv.ApplyStyleChanges ();
- // Ensures that the tab bar subview gets the bounds of the parent TabView
- tv.LayoutSubviews ();
-
- // Test two tab names that fit
+ // Test two tab names that fit
tab1.DisplayText = "12";
tab2.DisplayText = "13";
+ // Ensures that the tab bar subview gets the bounds of the parent TabView
+ tv.Layout ();
+
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@@ -678,8 +671,10 @@ public void ShowTopLine_False_TabsOnBottom_False_TestThinTabView_WithLongNames (
);
tv.SelectedTab = tab2;
+ Assert.Equal (tab2, tv.Subviews.First (v => v.Id.Contains ("tabRowView")).MostFocused);
- View.ClipToScreen ();
+ tv.Layout ();
+ View.SetClipToScreen ();
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@@ -697,8 +692,8 @@ public void ShowTopLine_False_TabsOnBottom_False_TestThinTabView_WithLongNames (
// Test first tab name too long
tab1.DisplayText = "12345678910";
tab2.DisplayText = "13";
-
- View.ClipToScreen ();
+ tv.Layout ();
+ View.SetClipToScreen ();
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@@ -713,9 +708,10 @@ public void ShowTopLine_False_TabsOnBottom_False_TestThinTabView_WithLongNames (
//switch to tab2
tv.SelectedTab = tab2;
- View.ClipToScreen ();
- tv.Draw ();
+ tv.Layout ();
+ View.SetClipToScreen ();
+ tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@"
│13│
@@ -730,9 +726,9 @@ public void ShowTopLine_False_TabsOnBottom_False_TestThinTabView_WithLongNames (
tab1.DisplayText = "12345678910";
tab2.DisplayText = "abcdefghijklmnopq";
- View.ClipToScreen ();
+ tv.Layout ();
+ View.SetClipToScreen ();
tv.Draw ();
-
TestHelpers.AssertDriverContentsWithFrameAre (
@"
│abcdefg│
@@ -753,9 +749,8 @@ public void ShowTopLine_False_TabsOnBottom_True_TestTabView_Width3 ()
tv.Height = 5;
tv.Style = new () { ShowTopLine = false, TabsOnBottom = true };
tv.ApplyStyleChanges ();
- tv.LayoutSubviews ();
+ tv.Layout ();
- View.ClipToScreen ();
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@@ -778,7 +773,7 @@ public void ShowTopLine_False_TabsOnBottom_True_TestTabView_Width4 ()
tv.Height = 5;
tv.Style = new () { ShowTopLine = false, TabsOnBottom = true };
tv.ApplyStyleChanges ();
- tv.LayoutSubviews ();
+ tv.Layout ();
tv.Draw ();
@@ -802,15 +797,13 @@ public void ShowTopLine_False_TabsOnBottom_True_TestThinTabView_WithLongNames ()
tv.Height = 5;
tv.Style = new () { ShowTopLine = false, TabsOnBottom = true };
tv.ApplyStyleChanges ();
+ tv.Layout ();
- // Ensures that the tab bar subview gets the bounds of the parent TabView
- tv.LayoutSubviews ();
-
- // Test two tab names that fit
+ // Test two tab names that fit
tab1.DisplayText = "12";
tab2.DisplayText = "13";
- View.ClipToScreen ();
+ tv.Layout ();
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@@ -824,8 +817,10 @@ public void ShowTopLine_False_TabsOnBottom_True_TestThinTabView_WithLongNames ()
);
tv.SelectedTab = tab2;
+ Assert.Equal (tab2, tv.Subviews.First (v => v.Id.Contains ("tabRowView")).MostFocused);
- View.ClipToScreen ();
+ tv.Layout ();
+ View.SetClipToScreen ();
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@@ -844,7 +839,8 @@ public void ShowTopLine_False_TabsOnBottom_True_TestThinTabView_WithLongNames ()
tab1.DisplayText = "12345678910";
tab2.DisplayText = "13";
- View.ClipToScreen ();
+ tv.Layout ();
+ View.SetClipToScreen ();
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@@ -859,7 +855,9 @@ public void ShowTopLine_False_TabsOnBottom_True_TestThinTabView_WithLongNames ()
//switch to tab2
tv.SelectedTab = tab2;
- View.ClipToScreen ();
+
+ tv.Layout ();
+ View.SetClipToScreen ();
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@@ -876,7 +874,8 @@ public void ShowTopLine_False_TabsOnBottom_True_TestThinTabView_WithLongNames ()
tab1.DisplayText = "12345678910";
tab2.DisplayText = "abcdefghijklmnopq";
- View.ClipToScreen ();
+ tv.Layout ();
+ View.SetClipToScreen ();
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@@ -899,7 +898,6 @@ public void ShowTopLine_True_TabsOnBottom_False_TestTabView_Width3 ()
tv.Height = 5;
tv.Layout ();
- View.ClipToScreen ();
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@@ -922,7 +920,7 @@ public void ShowTopLine_True_TabsOnBottom_False_TestTabView_Width4 ()
tv.Height = 5;
tv.Layout ();
- View.ClipToScreen ();
+ View.SetClipToScreen ();
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@@ -944,14 +942,11 @@ public void ShowTopLine_True_TabsOnBottom_False_TestThinTabView_WithLongNames ()
tv.Width = 10;
tv.Height = 5;
- // Ensures that the tab bar subview gets the bounds of the parent TabView
- tv.LayoutSubviews ();
-
- // Test two tab names that fit
+ // Test two tab names that fit
tab1.DisplayText = "12";
tab2.DisplayText = "13";
- View.ClipToScreen ();
+ tv.Layout ();
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@@ -966,7 +961,8 @@ public void ShowTopLine_True_TabsOnBottom_False_TestThinTabView_WithLongNames ()
tv.SelectedTab = tab2;
- View.ClipToScreen ();
+ tv.Layout ();
+ View.SetClipToScreen ();
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@@ -985,7 +981,8 @@ public void ShowTopLine_True_TabsOnBottom_False_TestThinTabView_WithLongNames ()
tab1.DisplayText = "12345678910";
tab2.DisplayText = "13";
- View.ClipToScreen ();
+ tv.Layout ();
+ View.SetClipToScreen ();
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@@ -1000,7 +997,9 @@ public void ShowTopLine_True_TabsOnBottom_False_TestThinTabView_WithLongNames ()
//switch to tab2
tv.SelectedTab = tab2;
- View.ClipToScreen ();
+
+ tv.Layout ();
+ View.SetClipToScreen ();
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@@ -1017,7 +1016,8 @@ public void ShowTopLine_True_TabsOnBottom_False_TestThinTabView_WithLongNames ()
tab1.DisplayText = "12345678910";
tab2.DisplayText = "abcdefghijklmnopq";
- View.ClipToScreen ();
+ tv.Layout ();
+ View.SetClipToScreen ();
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@@ -1039,13 +1039,11 @@ public void ShowTopLine_True_TabsOnBottom_False_With_Unicode ()
tv.Width = 20;
tv.Height = 5;
- tv.LayoutSubviews ();
-
tab1.DisplayText = "Tab0";
tab2.DisplayText = "Les Mise" + char.ConvertFromUtf32 (int.Parse ("0301", NumberStyles.HexNumber)) + "rables";
- View.ClipToScreen ();
+ tv.Layout ();
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@@ -1060,7 +1058,8 @@ public void ShowTopLine_True_TabsOnBottom_False_With_Unicode ()
tv.SelectedTab = tab2;
- View.ClipToScreen ();
+ tv.Layout ();
+ View.SetClipToScreen ();
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@@ -1083,7 +1082,7 @@ public void ShowTopLine_True_TabsOnBottom_True_TestTabView_Width3 ()
tv.Height = 5;
tv.Style = new () { TabsOnBottom = true };
tv.ApplyStyleChanges ();
- tv.LayoutSubviews ();
+ tv.Layout ();
tv.Draw ();
@@ -1107,7 +1106,7 @@ public void ShowTopLine_True_TabsOnBottom_True_TestTabView_Width4 ()
tv.Height = 5;
tv.Style = new () { TabsOnBottom = true };
tv.ApplyStyleChanges ();
- tv.LayoutSubviews ();
+ tv.Layout ();
tv.Draw ();
@@ -1131,15 +1130,13 @@ public void ShowTopLine_True_TabsOnBottom_True_TestThinTabView_WithLongNames ()
tv.Height = 5;
tv.Style = new () { TabsOnBottom = true };
tv.ApplyStyleChanges ();
+ tv.Layout ();
- // Ensures that the tab bar subview gets the bounds of the parent TabView
- tv.LayoutSubviews ();
-
- // Test two tab names that fit
+ // Test two tab names that fit
tab1.DisplayText = "12";
tab2.DisplayText = "13";
- View.ClipToScreen ();
+ tv.Layout ();
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@@ -1156,7 +1153,8 @@ public void ShowTopLine_True_TabsOnBottom_True_TestThinTabView_WithLongNames ()
tab1.DisplayText = "12345678910";
tab2.DisplayText = "13";
- View.ClipToScreen ();
+ tv.Layout ();
+ View.SetClipToScreen ();
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@@ -1171,7 +1169,9 @@ public void ShowTopLine_True_TabsOnBottom_True_TestThinTabView_WithLongNames ()
//switch to tab2
tv.SelectedTab = tab2;
- View.ClipToScreen ();
+
+ tv.Layout ();
+ View.SetClipToScreen ();
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@@ -1188,7 +1188,8 @@ public void ShowTopLine_True_TabsOnBottom_True_TestThinTabView_WithLongNames ()
tab1.DisplayText = "12345678910";
tab2.DisplayText = "abcdefghijklmnopq";
- View.ClipToScreen ();
+ tv.Layout ();
+ View.SetClipToScreen ();
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@@ -1212,12 +1213,11 @@ public void ShowTopLine_True_TabsOnBottom_True_With_Unicode ()
tv.Style = new () { TabsOnBottom = true };
tv.ApplyStyleChanges ();
- tv.LayoutSubviews ();
-
tab1.DisplayText = "Tab0";
tab2.DisplayText = "Les Mise" + char.ConvertFromUtf32 (int.Parse ("0301", NumberStyles.HexNumber)) + "rables";
+ tv.Layout ();
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@@ -1232,7 +1232,8 @@ public void ShowTopLine_True_TabsOnBottom_True_With_Unicode ()
tv.SelectedTab = tab2;
- View.ClipToScreen ();
+ tv.Layout ();
+ View.SetClipToScreen ();
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (
@@ -1322,6 +1323,138 @@ public void RemoveTab_ThatHasFocus ()
Application.Shutdown ();
}
+ [Fact]
+ [SetupFakeDriver]
+ public void Add_Three_TabsOnTop_ChangesTab ()
+ {
+ TabView tv = GetTabView (out Tab tab1, out Tab tab2, false);
+ Tab tab3;
+
+ tv.AddTab (
+ tab3 = new () { Id = "tab3", DisplayText = "Tab3", View = new TextView { Id = "tab3.TextView", Width = 3, Height = 1, Text = "hi3" } },
+ false);
+
+ tv.Width = 20;
+ tv.Height = 5;
+
+ tv.Layout ();
+ tv.Draw ();
+
+ Assert.Equal (tab1, tv.SelectedTab);
+
+ TestHelpers.AssertDriverContentsAre (
+ @"
+╭────┬────┬────╮
+│Tab1│Tab2│Tab3│
+│ ╰────┴────┴───╮
+│hi │
+└──────────────────┘
+",
+ output
+ );
+
+ tv.SelectedTab = tab2;
+
+ tv.Layout ();
+ View.SetClipToScreen ();
+ tv.Draw ();
+
+ TestHelpers.AssertDriverContentsWithFrameAre (
+ @"
+╭────┬────┬────╮
+│Tab1│Tab2│Tab3│
+├────╯ ╰────┴───╮
+│hi2 │
+└──────────────────┘
+",
+ output
+ );
+
+ tv.SelectedTab = tab3;
+
+ tv.Layout ();
+ View.SetClipToScreen ();
+ tv.Draw ();
+
+ TestHelpers.AssertDriverContentsWithFrameAre (
+ @"
+╭────┬────┬────╮
+│Tab1│Tab2│Tab3│
+├────┴────╯ ╰───╮
+│hi3 │
+└──────────────────┘
+",
+ output
+ );
+ }
+
+ [Fact]
+ [SetupFakeDriver]
+ public void Add_Three_TabsOnBottom_ChangesTab ()
+ {
+ TabView tv = GetTabView (out Tab tab1, out Tab tab2, false);
+ Tab tab3;
+
+ tv.AddTab (
+ tab3 = new () { Id = "tab3", DisplayText = "Tab3", View = new TextView { Id = "tab3.TextView", Width = 3, Height = 1, Text = "hi3" } },
+ false);
+
+ tv.Width = 20;
+ tv.Height = 5;
+ tv.Style = new () { TabsOnBottom = true };
+ tv.ApplyStyleChanges ();
+
+ tv.Layout ();
+ tv.Draw ();
+
+ Assert.Equal (tab1, tv.SelectedTab);
+
+ TestHelpers.AssertDriverContentsAre (
+ @"
+┌──────────────────┐
+│hi │
+│ ╭────┬────┬───╯
+│Tab1│Tab2│Tab3│
+╰────┴────┴────╯
+",
+ output
+ );
+
+ tv.SelectedTab = tab2;
+
+ tv.Layout ();
+ View.SetClipToScreen ();
+ tv.Draw ();
+
+ TestHelpers.AssertDriverContentsWithFrameAre (
+ @"
+┌──────────────────┐
+│hi2 │
+├────╮ ╭────┬───╯
+│Tab1│Tab2│Tab3│
+╰────┴────┴────╯
+",
+ output
+ );
+
+ tv.SelectedTab = tab3;
+
+ tv.Layout ();
+ View.SetClipToScreen ();
+ tv.Draw ();
+
+ TestHelpers.AssertDriverContentsWithFrameAre (
+ @"
+┌──────────────────┐
+│hi3 │
+├────┬────╮ ╭───╯
+│Tab1│Tab2│Tab3│
+╰────┴────┴────╯
+",
+ output
+ );
+ }
+
private TabView GetTabView () { return GetTabView (out _, out _); }
private TabView GetTabView (out Tab tab1, out Tab tab2, bool initFakeDriver = true)
@@ -1355,4 +1488,3 @@ private void InitFakeDriver ()
driver.Init ();
}
}
-#endif