diff --git a/Terminal.Gui/Views/Tab.cs b/Terminal.Gui/Views/Tab.cs
index 0c0be4a2c..975d535d5 100644
--- a/Terminal.Gui/Views/Tab.cs
+++ b/Terminal.Gui/Views/Tab.cs
@@ -1,25 +1,25 @@
namespace Terminal.Gui;
///
-/// A single tab in a
+/// A single tab in a .
///
-public class Tab {
- private string text;
+public class Tab : View {
+ private string _displayText;
///
- /// The text to display in a
+ /// The text to display in a .
///
///
- public string Text { get => text ?? "Unamed"; set => text = value; }
+ public string DisplayText { get => _displayText ?? "Unamed"; set => _displayText = value; }
///
- /// The control to display when the tab is selected
+ /// The control to display when the tab is selected.
///
///
public View View { get; set; }
///
- /// Creates a new unamed tab with no controls inside
+ /// Creates a new unamed tab with no controls inside.
///
public Tab ()
{
@@ -27,13 +27,16 @@ public class Tab {
}
///
- /// Creates a new tab with the given text hosting a view
+ /// Creates a new tab with the given text hosting a view.
///
- ///
- ///
- public Tab (string text, View view)
+ /// The real text.
+ /// The hosted view.
+ public Tab (string displayText, View view)
{
- this.Text = text;
+ this.DisplayText = displayText;
this.View = view;
+ BorderStyle = LineStyle.Rounded;
+ CanFocus = true;
+ Visible = false;
}
}
diff --git a/Terminal.Gui/Views/TabView.cs b/Terminal.Gui/Views/TabView.cs
index 86a558b82..4cb2beef4 100644
--- a/Terminal.Gui/Views/TabView.cs
+++ b/Terminal.Gui/Views/TabView.cs
@@ -7,40 +7,45 @@ using System.Linq;
namespace Terminal.Gui {
///
- /// Control that hosts multiple sub views, presenting a single one at once
+ /// Control that hosts multiple sub views, presenting a single one at once.
///
public class TabView : View {
- private Tab selectedTab;
+ private Tab _selectedTab;
///
- /// The default to set on new controls
+ /// The default to set on new controls.
///
public const uint DefaultMaxTabTextWidth = 30;
///
- /// This sub view is the 2 or 3 line control that represents the actual tabs themselves
+ /// This sub view is the 2 or 3 line control that represents the actual tabs themselves.
///
- TabRowView tabsBar;
+ TabRowView _tabsBar;
///
/// This sub view is the main client area of the current tab. It hosts the
- /// of the tab, the
+ /// of the tab, the .
///
- View contentView;
- private List tabs = new List ();
+ View _contentView;
+ private List _tabs = new List ();
///
- /// All tabs currently hosted by the control
+ /// All tabs currently hosted by the control.
///
///
- public IReadOnlyCollection Tabs { get => tabs.AsReadOnly (); }
+ public IReadOnlyCollection Tabs { get => _tabs.AsReadOnly (); }
///
/// When there are too many tabs to render, this indicates the first
/// tab to render on the screen.
///
///
- public int TabScrollOffset { get; set; }
+ public int TabScrollOffset {
+ get => _tabScrollOffset;
+ set {
+ _tabScrollOffset = EnsureValidScrollOffsets (value);
+ }
+ }
///
/// The maximum number of characters to render in a Tab header. This prevents one long tab
@@ -49,7 +54,7 @@ namespace Terminal.Gui {
public uint MaxTabTextWidth { get; set; } = DefaultMaxTabTextWidth;
///
- /// Event for when changes
+ /// Event for when changes.
///
public event EventHandler SelectedTabChanged;
@@ -60,44 +65,45 @@ namespace Terminal.Gui {
public event EventHandler TabClicked;
///
- /// The currently selected member of chosen by the user
+ /// The currently selected member of chosen by the user.
///
///
public Tab SelectedTab {
- get => selectedTab;
+ get => _selectedTab;
set {
+ UnSetCurrentTabs ();
- var old = selectedTab;
+ var old = _selectedTab;
- if (selectedTab != null) {
-
- if (selectedTab.View != null) {
+ if (_selectedTab != null) {
+ if (_selectedTab.View != null) {
// remove old content
- contentView.Remove (selectedTab.View);
+ _contentView.Remove (_selectedTab.View);
}
}
- selectedTab = value;
+ _selectedTab = value;
if (value != null) {
-
// add new content
- if (selectedTab.View != null) {
- contentView.Add (selectedTab.View);
+ if (_selectedTab.View != null) {
+ _contentView.Add (_selectedTab.View);
}
}
EnsureSelectedTabIsVisible ();
if (old != value) {
+ if (old?.HasFocus == true) {
+ SelectedTab.SetFocus ();
+ }
OnSelectedTabChanged (old, value);
}
-
}
}
///
- /// Render choices for how to display tabs. After making changes, call
+ /// Render choices for how to display tabs. After making changes, call .
///
///
public TabStyle Style { get; set; } = new TabStyle ();
@@ -108,101 +114,112 @@ namespace Terminal.Gui {
public TabView () : base ()
{
CanFocus = true;
- contentView = new View ();
- tabsBar = new TabRowView (this);
+ _tabsBar = new TabRowView (this);
+ _contentView = new View ();
ApplyStyleChanges ();
- base.Add (tabsBar);
- base.Add (contentView);
+ base.Add (_tabsBar);
+ base.Add (_contentView);
// Things this view knows how to do
AddCommand (Command.Left, () => { SwitchTabBy (-1); return true; });
AddCommand (Command.Right, () => { SwitchTabBy (1); return true; });
- AddCommand (Command.LeftHome, () => { SelectedTab = Tabs.FirstOrDefault (); return true; });
- AddCommand (Command.RightEnd, () => { SelectedTab = Tabs.LastOrDefault (); return true; });
+ AddCommand (Command.LeftHome, () => { TabScrollOffset = 0; SelectedTab = Tabs.FirstOrDefault (); return true; });
+ AddCommand (Command.RightEnd, () => { TabScrollOffset = Tabs.Count - 1; SelectedTab = Tabs.LastOrDefault (); return true; });
+ AddCommand (Command.NextView, () => { _contentView.SetFocus (); return true; });
+ AddCommand (Command.PreviousView, () => { SuperView?.FocusPrev (); 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
AddKeyBinding (Key.CursorLeft, Command.Left);
AddKeyBinding (Key.CursorRight, Command.Right);
AddKeyBinding (Key.Home, Command.LeftHome);
AddKeyBinding (Key.End, Command.RightEnd);
+ AddKeyBinding (Key.CursorDown, Command.NextView);
+ AddKeyBinding (Key.CursorUp, Command.PreviousView);
+ AddKeyBinding (Key.PageDown, Command.PageDown);
+ AddKeyBinding (Key.PageUp, Command.PageUp);
}
///
/// 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
+ /// to .
///
public void ApplyStyleChanges ()
{
- contentView.X = Style.ShowBorder ? 1 : 0;
- contentView.Width = Dim.Fill (Style.ShowBorder ? 1 : 0);
+ _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
- contentView.Y = Style.ShowBorder ? 1 : 0;
+ if (Style.ShowBorder) {
+ _contentView.Border.Thickness = new Thickness (1, 1, 1, 0);
+ }
- // Fill client area leaving space at bottom for tabs
- contentView.Height = Dim.Fill (GetTabHeight (false));
+ _contentView.Y = 0;
var tabHeight = GetTabHeight (false);
- tabsBar.Height = tabHeight;
- tabsBar.Y = Pos.Percent (100) - tabHeight;
+ // 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;
var tabHeight = GetTabHeight (true);
//move content down to make space for tabs
- contentView.Y = tabHeight;
+ _contentView.Y = Pos.Bottom (_tabsBar);
// Fill client area leaving space at bottom for border
- contentView.Height = Dim.Fill (Style.ShowBorder ? 1 : 0);
+ _contentView.Height = Dim.Fill ();
// The top tab should be 2 or 3 rows high and on the top
- tabsBar.Height = tabHeight;
+ _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
- tabsBar.Y = Pos.Percent (0);
}
LayoutSubviews ();
SetNeedsDisplay ();
}
-
///
public override void OnDrawContent (Rect contentArea)
{
- Move (0, 0);
Driver.SetAttribute (GetNormalColor ());
- if (Style.ShowBorder) {
-
- // How much space do we need to leave at the bottom to show the tabs
- int spaceAtBottom = Math.Max (0, GetTabHeight (false) - 1);
- int startAtY = Math.Max (0, GetTabHeight (true) - 1);
-
- Border.DrawFrame (new Rect (0, startAtY, Bounds.Width,
- Math.Max (Bounds.Height - spaceAtBottom - startAtY, 0)), false);
- }
-
if (Tabs.Any ()) {
- tabsBar.OnDrawContent (contentArea);
- contentView.SetNeedsDisplay ();
- var savedClip = contentView.ClipToBounds ();
- contentView.Draw ();
+ var savedClip = ClipToBounds ();
+ _tabsBar.OnDrawContent (contentArea);
+ _contentView.SetNeedsDisplay ();
+ _contentView.Draw ();
Driver.Clip = savedClip;
}
}
+ ///
+ public override void OnDrawContentComplete (Rect contentArea)
+ {
+ _tabsBar.OnDrawContentComplete (contentArea);
+ }
+
///
- /// Disposes the control and all
+ /// Disposes the control and all .
///
///
protected override void Dispose (bool disposing)
@@ -216,12 +233,11 @@ namespace Terminal.Gui {
if (!Equals (SelectedTab, tab)) {
tab.View?.Dispose ();
}
-
}
}
///
- /// Raises the event
+ /// Raises the event.
///
protected virtual void OnSelectedTabChanged (Tab oldTab, Tab newTab)
{
@@ -232,7 +248,7 @@ namespace Terminal.Gui {
///
public override bool ProcessKey (KeyEvent keyEvent)
{
- if (HasFocus && CanFocus && Focused == tabsBar) {
+ if (HasFocus && CanFocus && Focused == _tabsBar) {
var result = InvokeKeybindings (keyEvent);
if (result != null)
return (bool)result;
@@ -242,9 +258,9 @@ namespace Terminal.Gui {
}
///
- /// Changes the by the given .
+ /// Changes the by the given .
/// Positive for right, negative for left. If no tab is currently selected then
- /// the first tab will become selected
+ /// the first tab will become selected.
///
///
public void SwitchTabBy (int amount)
@@ -271,23 +287,25 @@ namespace Terminal.Gui {
var newIdx = Math.Max (0, Math.Min (currentIdx + amount, Tabs.Count - 1));
- SelectedTab = tabs [newIdx];
+ SelectedTab = _tabs [newIdx];
SetNeedsDisplay ();
EnsureSelectedTabIsVisible ();
}
///
- /// Updates to be a valid index of
+ /// Updates to be a valid index of .
///
- /// Changes will not be immediately visible in the display until you call
- public void EnsureValidScrollOffsets ()
+ /// 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)
{
- TabScrollOffset = Math.Max (Math.Min (TabScrollOffset, Tabs.Count - 1), 0);
+ return Math.Max (Math.Min (value, Tabs.Count - 1), 0);
}
///
- /// Updates to ensure that is visible
+ /// Updates to ensure that is visible.
///
public void EnsureSelectedTabIsVisible ()
{
@@ -309,7 +327,7 @@ namespace Terminal.Gui {
/// and you ask for ).
///
/// True to measure the space required at the top of the control,
- /// false to measure space at the bottom
+ /// false to measure space at the bottom..
///
private int GetTabHeight (bool top)
{
@@ -324,61 +342,104 @@ namespace Terminal.Gui {
return Style.ShowTopLine ? 3 : 2;
}
+ private TabToRender [] _tabLocations;
+ private int _tabScrollOffset;
+
///
- /// Returns which tabs to render at each x location
+ /// Returns which tabs to render at each x location.
///
///
private IEnumerable CalculateViewport (Rect bounds)
{
+ UnSetCurrentTabs ();
+
int i = 1;
+ View prevTab = null;
// Starting at the first or scrolled to tab
foreach (var tab in Tabs.Skip (TabScrollOffset)) {
- // while there is space for the tab
- var tabTextWidth = tab.Text.EnumerateRunes ().Sum (c => c.GetColumns ());
+ if (prevTab != null) {
+ tab.X = Pos.Right (prevTab);
+ } else {
+ tab.X = 0;
+ }
+ tab.Y = 0;
- string text = tab.Text;
+ // while there is space for the tab
+ var 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!
var 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 (i, tab, string.Empty, Equals (SelectedTab, tab), 0);
break;
}
if (tabTextWidth > maxWidth) {
- text = tab.Text.Substring (0, (int)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 (i, tab, text, Equals (SelectedTab, tab), tabTextWidth);
i += tabTextWidth + 1;
}
}
+ private void UnSetCurrentTabs ()
+ {
+ if (_tabLocations != null) {
+ foreach (var tabToRender in _tabLocations) {
+ tabToRender.Tab.MouseClick -= Tab_MouseClick;
+ tabToRender.Tab.Visible = false;
+ }
+ _tabLocations = null;
+ }
+ }
+
+ private void Tab_MouseClick (object sender, MouseEventEventArgs e)
+ {
+ e.Handled = _tabsBar.MouseEvent (e.MouseEvent);
+ }
+
///
- /// Adds the given to
+ /// Adds the given to .
///
///
- /// True to make the newly added Tab the
+ /// True to make the newly added Tab the .
public void AddTab (Tab tab, bool andSelect)
{
- if (tabs.Contains (tab)) {
+ if (_tabs.Contains (tab)) {
return;
}
- tabs.Add (tab);
+ _tabs.Add (tab);
+ _tabsBar.Add (tab);
if (SelectedTab == null || andSelect) {
SelectedTab = tab;
@@ -399,14 +460,14 @@ namespace Terminal.Gui {
///
public void RemoveTab (Tab tab)
{
- if (tab == null || !tabs.Contains (tab)) {
+ if (tab == null || !_tabs.Contains (tab)) {
return;
}
// what tab was selected before closing
- var idx = tabs.IndexOf (tab);
+ var idx = _tabs.IndexOf (tab);
- tabs.Remove (tab);
+ _tabs.Remove (tab);
// if the currently selected tab is no longer a member of Tabs
if (SelectedTab == null || !Tabs.Contains (SelectedTab)) {
@@ -430,7 +491,7 @@ namespace Terminal.Gui {
public Tab Tab { get; set; }
///
- /// True if the tab that is being rendered is the selected one
+ /// True if the tab that is being rendered is the selected one.
///
///
public bool IsSelected { get; set; }
@@ -448,16 +509,24 @@ namespace Terminal.Gui {
}
private class TabRowView : View {
-
- readonly TabView host;
+ readonly TabView _host;
+ View _rightScrollIndicator;
+ View _leftScrollIndicator;
public TabRowView (TabView host)
{
- this.host = host;
+ this._host = host;
CanFocus = true;
Height = 1;
Width = Dim.Fill ();
+
+ _rightScrollIndicator = new View () { Id = "rightScrollIndicator", Width = 1, Height = 1, Visible = false, Text = CM.Glyphs.RightArrow.ToString () };
+ _rightScrollIndicator.MouseClick += _host.Tab_MouseClick;
+ _leftScrollIndicator = new View () { Id = "leftScrollIndicator", Width = 1, Height = 1, Visible = false , Text = CM.Glyphs.LeftArrow.ToString ()};
+ _leftScrollIndicator.MouseClick += _host.Tab_MouseClick;
+
+ Add (_rightScrollIndicator, _leftScrollIndicator);
}
public override bool OnEnter (View view)
@@ -468,195 +537,353 @@ namespace Terminal.Gui {
public override void OnDrawContent (Rect contentArea)
{
- var tabLocations = host.CalculateViewport (Bounds).ToArray ();
- var width = Bounds.Width;
- Driver.SetAttribute (GetNormalColor ());
+ _host._tabLocations = _host.CalculateViewport (Bounds).ToArray ();
- if (host.Style.ShowTopLine) {
- RenderOverline (tabLocations, width);
- }
+ // clear any old text
+ Clear ();
- RenderTabLine (tabLocations, width);
+ RenderTabLine ();
- RenderUnderline (tabLocations, width);
+ RenderUnderline ();
Driver.SetAttribute (GetNormalColor ());
}
- ///
- /// Renders the line of the tabs that does not adjoin the content
- ///
- ///
- ///
- private void RenderOverline (TabToRender [] tabLocations, int width)
+ public override void OnDrawContentComplete (Rect contentArea)
{
- // if tabs are on the bottom draw the side of the tab that doesn't border the content area at the bottom otherwise the top
- int y = host.Style.TabsOnBottom ? 2 : 0;
-
- Move (0, y);
-
- var selected = tabLocations.FirstOrDefault (t => t.IsSelected);
-
- // Clear out everything
- Driver.AddStr (new string (' ', width));
-
- // Nothing is selected... odd but we are done
- if (selected == null) {
+ if (_host._tabLocations == null) {
return;
}
- Move (selected.X - 1, y);
- Driver.AddRune (host.Style.TabsOnBottom ? CM.Glyphs.LLCorner : CM.Glyphs.ULCorner);
+ var tabLocations = _host._tabLocations;
+ var selectedTab = -1;
- for (int i = 0; i < selected.Width; i++) {
+ for (int i = 0; i < tabLocations.Length; i++) {
+ View tab = tabLocations [i].Tab;
+ var vts = tab.BoundsToScreen (tab.Bounds);
+ LineCanvas lc = new LineCanvas ();
+ var selectedOffset = _host.Style.ShowTopLine && tabLocations [i].IsSelected ? 0 : 1;
- if (selected.X + i > width) {
- // we ran out of space horizontally
- return;
- }
+ if (tabLocations [i].IsSelected) {
+ selectedTab = i;
- Driver.AddRune (CM.Glyphs.HLine);
- }
+ 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);
+ }
- // Add the end of the selected tab
- Driver.AddRune (host.Style.TabsOnBottom ? CM.Glyphs.LRCorner : CM.Glyphs.URCorner);
+ 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);
+ }
+ }
+ }
- }
-
- ///
- /// Renders the line with the tab names in it
- ///
- ///
- ///
- private void RenderTabLine (TabToRender [] tabLocations, int width)
- {
- int y;
-
- if (host.Style.TabsOnBottom) {
-
- y = 1;
- } else {
- y = host.Style.ShowTopLine ? 1 : 0;
- }
-
-
- // clear any old text
- Move (0, y);
- Driver.AddStr (new string (' ', width));
-
- foreach (var toRender in tabLocations) {
-
- if (toRender.IsSelected) {
- Move (toRender.X - 1, y);
- Driver.AddRune (CM.Glyphs.VLine);
- }
-
- Move (toRender.X, y);
-
- // 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 ourself then show that they can switch tabs
- Driver.SetAttribute (ColorScheme.HotFocus);
+ 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);
+ }
- // Focus is inside the tab
- Driver.SetAttribute (ColorScheme.HotNormal);
+ } 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);
}
}
- Driver.AddStr (toRender.TextToRender);
- Driver.SetAttribute (GetNormalColor ());
-
- if (toRender.IsSelected) {
- Driver.AddRune (CM.Glyphs.VLine);
+ 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;
+ var lastSelectedTab = !_host.Style.ShowTopLine && i == selectedTab ? 1 : _host.Style.TabsOnBottom ? 1 : 0;
+ var tabsBarVts = BoundsToScreen (Bounds);
+ var 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.OnRenderLineCanvas ();
}
}
///
- /// Renders the line of the tab that adjoins the content of the tab
+ /// Renders the line with the tab names in it.
///
- ///
- ///
- private void RenderUnderline (TabToRender [] tabLocations, int width)
+ private void RenderTabLine ()
+ {
+ var tabLocations = _host._tabLocations;
+ int y;
+
+ if (_host.Style.TabsOnBottom) {
+
+ y = 1;
+ } else {
+ y = _host.Style.ShowTopLine ? 1 : 0;
+ }
+
+ View selected = null;
+ var topLine = _host.Style.ShowTopLine ? 1 : 0;
+ var width = Bounds.Width;
+
+ foreach (var toRender in tabLocations) {
+ var 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 == 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.Anchor (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.Anchor (0) - 1, 1);
+ }
+
+ tab.Text = toRender.TextToRender;
+
+ LayoutSubviews ();
+
+ tab.OnDrawFrames ();
+
+ var prevAttr = Driver.GetAttribute ();
+
+ // 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 ourself then show that they can switch tabs
+ prevAttr = ColorScheme.HotFocus;
+ } else {
+
+ // Focus is inside the tab
+ prevAttr = ColorScheme.HotNormal;
+ }
+ }
+ tab.TextFormatter.Draw (tab.BoundsToScreen (tab.Bounds), prevAttr, ColorScheme.HotNormal);
+
+ tab.OnRenderLineCanvas ();
+
+ Driver.SetAttribute (GetNormalColor ());
+ }
+ }
+
+ ///
+ /// Renders the line of the tab that adjoins the content of the tab.
+ ///
+ private void RenderUnderline ()
{
int y = GetUnderlineYPosition ();
- Move (0, y);
-
- // If host has no border then we need to draw the solid line first (then we draw gaps over the top)
- if (!host.Style.ShowBorder) {
-
- for (int x = 0; x < width; x++) {
- Driver.AddRune (CM.Glyphs.HLine);
- }
-
- }
- var selected = tabLocations.FirstOrDefault (t => t.IsSelected);
+ var selected = _host._tabLocations.FirstOrDefault (t => t.IsSelected);
if (selected == null) {
return;
}
- Move (selected.X - 1, y);
-
- Driver.AddRune (selected.X == 1 ? CM.Glyphs.VLine :
- (host.Style.TabsOnBottom ? CM.Glyphs.URCorner : CM.Glyphs.LRCorner));
-
- Driver.AddStr (new string (' ', selected.Width));
-
- Driver.AddRune (selected.X + selected.Width == width - 1 ?
- CM.Glyphs.VLine :
- (host.Style.TabsOnBottom ? CM.Glyphs.ULCorner : CM.Glyphs.LLCorner));
-
// draw scroll indicators
// if there are more tabs to the left not visible
- if (host.TabScrollOffset > 0) {
- Move (0, y);
+ if (_host.TabScrollOffset > 0) {
+ _leftScrollIndicator.X = 0;
+ _leftScrollIndicator .Y = y;
// indicate that
- Driver.AddRune (CM.Glyphs.LeftArrow);
+ _leftScrollIndicator.Visible = true;
+ // Ensures this is clicked instead of the first tab
+ BringSubviewToFront (_leftScrollIndicator);
+ _leftScrollIndicator.Draw ();
+ } else {
+ _leftScrollIndicator.Visible = false;
}
// if there are more tabs to the right not visible
- if (ShouldDrawRightScrollIndicator (tabLocations)) {
- Move (width - 1, y);
+ if (ShouldDrawRightScrollIndicator ()) {
+ _rightScrollIndicator.X = Bounds.Width - 1;
+ _rightScrollIndicator .Y = y;
// indicate that
- Driver.AddRune (CM.Glyphs.RightArrow);
+ _rightScrollIndicator.Visible = true;
+ // Ensures this is clicked instead of the last tab if under this
+ BringSubviewToFront (_rightScrollIndicator);
+ _rightScrollIndicator.Draw ();
+ } else {
+ _rightScrollIndicator.Visible = false;
}
}
- private bool ShouldDrawRightScrollIndicator (TabToRender [] tabLocations)
+ private bool ShouldDrawRightScrollIndicator ()
{
- return tabLocations.LastOrDefault ()?.Tab != host.Tabs.LastOrDefault ();
+ return _host._tabLocations.LastOrDefault ()?.Tab != _host.Tabs.LastOrDefault ();
}
private int GetUnderlineYPosition ()
{
- if (host.Style.TabsOnBottom) {
+ if (_host.Style.TabsOnBottom) {
return 0;
} else {
- return host.Style.ShowTopLine ? 2 : 1;
+ return _host.Style.ShowTopLine ? 2 : 1;
}
}
public override bool MouseEvent (MouseEvent me)
{
- var hit = ScreenToTab (me.X, me.Y);
+ var hit = me.View is Tab ? (Tab)me.View : null;
bool isClick = me.Flags.HasFlag (MouseFlags.Button1Clicked) ||
me.Flags.HasFlag (MouseFlags.Button2Clicked) ||
me.Flags.HasFlag (MouseFlags.Button3Clicked);
if (isClick) {
- host.OnTabClicked (new TabMouseEventArgs (hit, me));
+ _host.OnTabClicked (new TabMouseEventArgs (hit, me));
// user canceled click
if (me.Handled) {
@@ -677,18 +904,23 @@ namespace Terminal.Gui {
me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) ||
me.Flags.HasFlag (MouseFlags.Button1TripleClicked)) {
- var scrollIndicatorHit = ScreenToScrollIndicator (me.X, me.Y);
+ int scrollIndicatorHit = 0;
+ if (me.View != null && me.View.Id == "rightScrollIndicator") {
+ scrollIndicatorHit = 1;
+ } else if (me.View != null && me.View.Id == "leftScrollIndicator") {
+ scrollIndicatorHit = -1;
+ }
if (scrollIndicatorHit != 0) {
- host.SwitchTabBy (scrollIndicatorHit);
+ _host.SwitchTabBy (scrollIndicatorHit);
SetNeedsDisplay ();
return true;
}
if (hit != null) {
- host.SelectedTab = hit;
+ _host.SelectedTab = hit;
SetNeedsDisplay ();
return true;
}
@@ -696,43 +928,6 @@ namespace Terminal.Gui {
return false;
}
-
- ///
- /// Calculates whether scroll indicators are visible and if so whether the click
- /// was on one of them.
- ///
- ///
- ///
- /// -1 for click in scroll left, 1 for scroll right or 0 for no hit
- private int ScreenToScrollIndicator (int x, int y)
- {
- // scroll indicator is showing
- if (host.TabScrollOffset > 0 && x == 0) {
-
- return y == GetUnderlineYPosition () ? -1 : 0;
- }
-
- // scroll indicator is showing
- if (x == Bounds.Width - 1 && ShouldDrawRightScrollIndicator (host.CalculateViewport (Bounds).ToArray ())) {
-
- return y == GetUnderlineYPosition () ? 1 : 0;
- }
-
- return 0;
- }
-
- ///
- /// Translates the client coordinates of a click into a tab when the click is on top of a tab
- ///
- ///
- ///
- ///
- public Tab ScreenToTab (int x, int y)
- {
- var tabs = host.CalculateViewport (Bounds);
-
- return tabs.LastOrDefault (t => x >= t.X && x < t.X + t.Width)?.Tab;
- }
}
///
diff --git a/UICatalog/Scenarios/Notepad.cs b/UICatalog/Scenarios/Notepad.cs
index c521d896a..9ddb1a109 100644
--- a/UICatalog/Scenarios/Notepad.cs
+++ b/UICatalog/Scenarios/Notepad.cs
@@ -198,7 +198,7 @@ namespace UICatalog.Scenarios {
if (tab.UnsavedChanges) {
- int result = MessageBox.Query ("Save Changes", $"Save changes to {tab.Text.TrimEnd ('*')}", "Yes", "No", "Cancel");
+ int result = MessageBox.Query ("Save Changes", $"Save changes to {tab.DisplayText.TrimEnd ('*')}", "Yes", "No", "Cancel");
if (result == -1 || result == 2) {
@@ -321,7 +321,7 @@ namespace UICatalog.Scenarios {
}
tab.File = new FileInfo (fd.Path);
- tab.Text = fd.FileName;
+ tab.DisplayText = fd.FileName;
tab.Save ();
return true;
@@ -357,16 +357,16 @@ namespace UICatalog.Scenarios {
var areDiff = this.UnsavedChanges;
if (areDiff) {
- if (!this.Text.EndsWith ('*')) {
+ if (!this.DisplayText.EndsWith ('*')) {
- this.Text = this.Text + '*';
+ this.DisplayText = this.DisplayText + '*';
parent.SetNeedsDisplay ();
}
} else {
- if (Text.EndsWith ('*')) {
+ if (DisplayText.EndsWith ('*')) {
- Text = Text.TrimEnd ('*');
+ DisplayText = DisplayText.TrimEnd ('*');
parent.SetNeedsDisplay ();
}
}
@@ -392,7 +392,7 @@ namespace UICatalog.Scenarios {
}
public OpenedFile CloneTo(TabView other)
{
- var newTab = new OpenedFile (other, base.Text.ToString(), File);
+ var newTab = new OpenedFile (other, base.DisplayText.ToString(), File);
other.AddTab (newTab, true);
return newTab;
}
@@ -403,7 +403,7 @@ namespace UICatalog.Scenarios {
System.IO.File.WriteAllText (File.FullName, newText);
SavedText = newText;
- Text = Text.TrimEnd ('*');
+ DisplayText = DisplayText.TrimEnd ('*');
}
}
diff --git a/UICatalog/Scenarios/TabViewExample.cs b/UICatalog/Scenarios/TabViewExample.cs
index 190487751..b45729afa 100644
--- a/UICatalog/Scenarios/TabViewExample.cs
+++ b/UICatalog/Scenarios/TabViewExample.cs
@@ -1,11 +1,5 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using System.Linq;
using Terminal.Gui;
-using static UICatalog.Scenario;
namespace UICatalog.Scenarios {
@@ -18,6 +12,7 @@ namespace UICatalog.Scenarios {
MenuItem miShowTopLine;
MenuItem miShowBorder;
MenuItem miTabsOnBottom;
+ MenuItem miShowTabViewBorder;
public override void Setup ()
{
@@ -46,6 +41,10 @@ namespace UICatalog.Scenarios {
miTabsOnBottom = new MenuItem ("_Tabs On Bottom", "", () => SetTabsOnBottom()){
Checked = false,
CheckType = MenuItemCheckStyle.Checked
+ },
+ miShowTabViewBorder = new MenuItem ("_Show TabView Border", "", () => ShowTabViewBorder()){
+ Checked = true,
+ CheckType = MenuItemCheckStyle.Checked
}
})
@@ -57,6 +56,7 @@ namespace UICatalog.Scenarios {
Y = 0,
Width = 60,
Height = 20,
+ BorderStyle = LineStyle.Single
};
tabView.AddTab (new Tab ("Tab1", new Label ("hodor!")), false);
@@ -192,6 +192,15 @@ namespace UICatalog.Scenarios {
tabView.ApplyStyleChanges ();
}
+ private void ShowTabViewBorder ()
+ {
+ miShowTabViewBorder.Checked = !miShowTabViewBorder.Checked;
+
+ tabView.BorderStyle = miShowTabViewBorder.Checked == true ? tabView.BorderStyle = LineStyle.Single
+ : LineStyle.None;
+ tabView.ApplyStyleChanges ();
+ }
+
private void Quit ()
{
Application.RequestStop ();
diff --git a/UnitTests/Views/ButtonTests.cs b/UnitTests/Views/ButtonTests.cs
index 5097ade02..be9b90133 100644
--- a/UnitTests/Views/ButtonTests.cs
+++ b/UnitTests/Views/ButtonTests.cs
@@ -509,9 +509,9 @@ namespace Terminal.Gui.ViewsTests {
var btn3 = $"{CM.Glyphs.LeftBracket} Cancel {CM.Glyphs.RightBracket}";
var expected = @$"
┌────────────────────────────────────────────────────┐
-│┌────┐ │
+│╭────╮ │
││Find│ │
-││ └─────────────────────────────────────────────┐│
+││ ╰─────────────────────────────────────────────╮│
││ ││
││ Find: Testing buttons. {btn1} ││
││ {btn2} ││
diff --git a/UnitTests/Views/TabViewTests.cs b/UnitTests/Views/TabViewTests.cs
index ea049a299..f4da0896f 100644
--- a/UnitTests/Views/TabViewTests.cs
+++ b/UnitTests/Views/TabViewTests.cs
@@ -255,15 +255,15 @@ namespace Terminal.Gui.ViewsTests {
tv.LayoutSubviews ();
// Test two tab names that fit
- tab1.Text = "12";
- tab2.Text = "13";
+ tab1.DisplayText = "12";
+ tab2.DisplayText = "13";
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (@"
-┌──┐
-│12│13
-│ └─────┐
+╭──┬──╮
+│12│13│
+│ ╰──┴──╮
│hi │
└────────┘", output);
@@ -272,23 +272,23 @@ namespace Terminal.Gui.ViewsTests {
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (@"
- ┌──┐
- 12│13│
-┌──┘ └──┐
+╭──┬──╮
+│12│13│
+├──╯ ╰──╮
│hi2 │
└────────┘", output);
tv.SelectedTab = tab1;
// Test first tab name too long
- tab1.Text = "12345678910";
- tab2.Text = "13";
+ tab1.DisplayText = "12345678910";
+ tab2.DisplayText = "13";
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (@"
-┌───────┐
+╭───────╮
│1234567│
-│ └►
+│ ╰►
│hi │
└────────┘", output);
@@ -297,22 +297,22 @@ namespace Terminal.Gui.ViewsTests {
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (@"
-┌──┐
+╭──╮
│13│
-◄ └─────┐
+◄ ╰─────╮
│hi2 │
└────────┘", output);
// now make both tabs too long
- tab1.Text = "12345678910";
- tab2.Text = "abcdefghijklmnopq";
+ tab1.DisplayText = "12345678910";
+ tab2.DisplayText = "abcdefghijklmnopq";
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (@"
-┌───────┐
+╭───────╮
│abcdefg│
-◄ └┐
+◄ ╰╮
│hi2 │
└────────┘", output);
}
@@ -330,14 +330,14 @@ namespace Terminal.Gui.ViewsTests {
tv.LayoutSubviews ();
// Test two tab names that fit
- tab1.Text = "12";
- tab2.Text = "13";
+ tab1.DisplayText = "12";
+ tab2.DisplayText = "13";
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (@"
-│12│13
-│ └─────┐
+│12│13│
+│ ╰──┴──╮
│hi │
│ │
└────────┘", output);
@@ -347,8 +347,8 @@ namespace Terminal.Gui.ViewsTests {
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (@"
- 12│13│
-┌──┘ └──┐
+│12│13│
+├──╯ ╰──╮
│hi2 │
│ │
└────────┘", output);
@@ -356,14 +356,14 @@ namespace Terminal.Gui.ViewsTests {
tv.SelectedTab = tab1;
// Test first tab name too long
- tab1.Text = "12345678910";
- tab2.Text = "13";
+ tab1.DisplayText = "12345678910";
+ tab2.DisplayText = "13";
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (@"
│1234567│
-│ └►
+│ ╰►
│hi │
│ │
└────────┘", output);
@@ -374,20 +374,20 @@ namespace Terminal.Gui.ViewsTests {
TestHelpers.AssertDriverContentsWithFrameAre (@"
│13│
-◄ └─────┐
+◄ ╰─────╮
│hi2 │
│ │
└────────┘", output);
// now make both tabs too long
- tab1.Text = "12345678910";
- tab2.Text = "abcdefghijklmnopq";
+ tab1.DisplayText = "12345678910";
+ tab2.DisplayText = "abcdefghijklmnopq";
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (@"
│abcdefg│
-◄ └┐
+◄ ╰╮
│hi2 │
│ │
└────────┘", output);
@@ -404,9 +404,9 @@ namespace Terminal.Gui.ViewsTests {
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (@"
-┌─┐
+╭─╮
│T│
-│ └►
+│ ╰►
│hi│
└──┘", output);
}
@@ -425,7 +425,7 @@ namespace Terminal.Gui.ViewsTests {
TestHelpers.AssertDriverContentsWithFrameAre (@"
│T│
-│ └►
+│ ╰►
│hi│
│ │
└──┘", output);
@@ -442,9 +442,9 @@ namespace Terminal.Gui.ViewsTests {
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (@"
-┌┐
+╭╮
││
-│└►
+│╰►
│h│
└─┘", output);
}
@@ -463,7 +463,7 @@ namespace Terminal.Gui.ViewsTests {
TestHelpers.AssertDriverContentsWithFrameAre (@"
││
-│└►
+│╰►
│h│
│ │
└─┘", output);
@@ -482,30 +482,30 @@ namespace Terminal.Gui.ViewsTests {
tv.LayoutSubviews ();
// Test two tab names that fit
- tab1.Text = "12";
- tab2.Text = "13";
+ tab1.DisplayText = "12";
+ tab2.DisplayText = "13";
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (@"
┌────────┐
│hi │
-│ ┌─────┘
-│12│13
-└──┘ ", output);
+│ ╭──┬──╯
+│12│13│
+╰──┴──╯ ", output);
// Test first tab name too long
- tab1.Text = "12345678910";
- tab2.Text = "13";
+ tab1.DisplayText = "12345678910";
+ tab2.DisplayText = "13";
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (@"
┌────────┐
│hi │
-│ ┌►
+│ ╭►
│1234567│
-└───────┘ ", output);
+╰───────╯ ", output);
//switch to tab2
tv.SelectedTab = tab2;
@@ -514,22 +514,22 @@ namespace Terminal.Gui.ViewsTests {
TestHelpers.AssertDriverContentsWithFrameAre (@"
┌────────┐
│hi2 │
-◄ ┌─────┘
+◄ ╭─────╯
│13│
-└──┘ ", output);
+╰──╯ ", output);
// now make both tabs too long
- tab1.Text = "12345678910";
- tab2.Text = "abcdefghijklmnopq";
+ tab1.DisplayText = "12345678910";
+ tab2.DisplayText = "abcdefghijklmnopq";
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (@"
┌────────┐
│hi2 │
-◄ ┌┘
+◄ ╭╯
│abcdefg│
-└───────┘ ", output);
+╰───────╯ ", output);
}
[Fact, AutoInitShutdown]
@@ -545,8 +545,8 @@ namespace Terminal.Gui.ViewsTests {
tv.LayoutSubviews ();
// Test two tab names that fit
- tab1.Text = "12";
- tab2.Text = "13";
+ tab1.DisplayText = "12";
+ tab2.DisplayText = "13";
tv.Draw ();
@@ -554,8 +554,8 @@ namespace Terminal.Gui.ViewsTests {
┌────────┐
│hi │
│ │
-│ ┌─────┘
-│12│13 ", output);
+│ ╭──┬──╯
+│12│13│ ", output);
tv.SelectedTab = tab2;
@@ -565,14 +565,14 @@ namespace Terminal.Gui.ViewsTests {
┌────────┐
│hi2 │
│ │
-└──┐ ┌──┘
- 12│13│ ", output);
+├──╮ ╭──╯
+│12│13│ ", output);
tv.SelectedTab = tab1;
// Test first tab name too long
- tab1.Text = "12345678910";
- tab2.Text = "13";
+ tab1.DisplayText = "12345678910";
+ tab2.DisplayText = "13";
tv.Draw ();
@@ -580,7 +580,7 @@ namespace Terminal.Gui.ViewsTests {
┌────────┐
│hi │
│ │
-│ ┌►
+│ ╭►
│1234567│ ", output);
//switch to tab2
@@ -591,12 +591,12 @@ namespace Terminal.Gui.ViewsTests {
┌────────┐
│hi2 │
│ │
-◄ ┌─────┘
+◄ ╭─────╯
│13│ ", output);
// now make both tabs too long
- tab1.Text = "12345678910";
- tab2.Text = "abcdefghijklmnopq";
+ tab1.DisplayText = "12345678910";
+ tab2.DisplayText = "abcdefghijklmnopq";
tv.Draw ();
@@ -604,7 +604,7 @@ namespace Terminal.Gui.ViewsTests {
┌────────┐
│hi2 │
│ │
-◄ ┌┘
+◄ ╭╯
│abcdefg│ ", output);
}
@@ -623,9 +623,9 @@ namespace Terminal.Gui.ViewsTests {
TestHelpers.AssertDriverContentsWithFrameAre (@"
┌──┐
│hi│
-│ ┌►
+│ ╭►
│T│
-└─┘ ", output);
+╰─╯ ", output);
}
[Fact, AutoInitShutdown]
@@ -644,7 +644,7 @@ namespace Terminal.Gui.ViewsTests {
┌──┐
│hi│
│ │
-│ ┌►
+│ ╭►
│T│ ", output);
}
@@ -663,9 +663,9 @@ namespace Terminal.Gui.ViewsTests {
TestHelpers.AssertDriverContentsWithFrameAre (@"
┌─┐
│h│
-│┌►
+│╭►
││
-└┘ ", output);
+╰╯ ", output);
}
[Fact, AutoInitShutdown]
@@ -684,7 +684,7 @@ namespace Terminal.Gui.ViewsTests {
┌─┐
│h│
│ │
-│┌►
+│╭►
││ ", output);
}
@@ -697,15 +697,15 @@ namespace Terminal.Gui.ViewsTests {
tv.LayoutSubviews ();
- tab1.Text = "Tab0";
- tab2.Text = "Les Mise" + Char.ConvertFromUtf32 (Int32.Parse ("0301", NumberStyles.HexNumber)) + "rables";
+ tab1.DisplayText = "Tab0";
+ tab2.DisplayText = "Les Mise" + Char.ConvertFromUtf32 (Int32.Parse ("0301", NumberStyles.HexNumber)) + "rables";
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (@"
-┌────┐
+╭────╮
│Tab0│
-│ └─────────────►
+│ ╰─────────────►
│hi │
└──────────────────┘", output);
@@ -714,9 +714,9 @@ namespace Terminal.Gui.ViewsTests {
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (@"
-┌──────────────┐
+╭──────────────╮
│Les Misérables│
-◄ └───┐
+◄ ╰───╮
│hi2 │
└──────────────────┘", output);
}
@@ -732,17 +732,17 @@ namespace Terminal.Gui.ViewsTests {
tv.LayoutSubviews ();
- tab1.Text = "Tab0";
- tab2.Text = "Les Mise" + Char.ConvertFromUtf32 (Int32.Parse ("0301", NumberStyles.HexNumber)) + "rables";
+ tab1.DisplayText = "Tab0";
+ tab2.DisplayText = "Les Mise" + Char.ConvertFromUtf32 (Int32.Parse ("0301", NumberStyles.HexNumber)) + "rables";
tv.Draw ();
TestHelpers.AssertDriverContentsWithFrameAre (@"
┌──────────────────┐
│hi │
-│ ┌─────────────►
+│ ╭─────────────►
│Tab0│
-└────┘ ", output);
+╰────╯ ", output);
tv.SelectedTab = tab2;
@@ -751,9 +751,9 @@ namespace Terminal.Gui.ViewsTests {
TestHelpers.AssertDriverContentsWithFrameAre (@"
┌──────────────────┐
│hi2 │
-◄ ┌───┘
+◄ ╭───╯
│Les Misérables│
-└──────────────┘ ", output);
+╰──────────────╯ ", output);
}
[Fact, AutoInitShutdown]
@@ -768,82 +768,401 @@ namespace Terminal.Gui.ViewsTests {
tv.Draw ();
- var tabRow = tv.Subviews[0];
- Assert.Equal("TabRowView",tabRow.GetType().Name);
+ var tabRow = tv.Subviews [0];
+ Assert.Equal ("TabRowView", tabRow.GetType ().Name);
TestHelpers.AssertDriverContentsAre (@"
-┌────┐
-│Tab1│Tab2
-│ └─────────────┐
+╭────┬────╮
+│Tab1│Tab2│
+│ ╰────┴────────╮
│hi │
└──────────────────┘
", output);
Tab clicked = null;
-
- tv.TabClicked += (s,e)=>{
+ tv.TabClicked += (s, e) => {
clicked = e.Tab;
};
- // Waving mouse around does not trigger click
- for(int i=0;i<100;i++)
- {
- tabRow.MouseEvent(new MouseEvent{
- X = i,
- Y = 1,
- Flags = MouseFlags.ReportMousePosition
- });
+ Application.Top.Add (tv);
+ Application.Begin (Application.Top);
- Assert.Null(clicked);
- Assert.Equal(tab1, tv.SelectedTab);
+ MouseEventEventArgs args;
+
+ // Waving mouse around does not trigger click
+ for (int i = 0; i < 100; i++) {
+ args = new MouseEventEventArgs (new MouseEvent {
+ X = i,
+ Y = 1,
+ Flags = MouseFlags.ReportMousePosition
+ });
+ Application.OnMouseEvent (args);
+ Application.Refresh ();
+ Assert.Null (clicked);
+ Assert.Equal (tab1, tv.SelectedTab);
}
- tabRow.MouseEvent(new MouseEvent{
- X = 3,
- Y = 1,
- Flags = MouseFlags.Button1Clicked
+ args = new MouseEventEventArgs (new MouseEvent {
+ X = 3,
+ Y = 1,
+ Flags = MouseFlags.Button1Clicked
});
-
- Assert.Equal(tab1, clicked);
- Assert.Equal(tab1, tv.SelectedTab);
+ Application.OnMouseEvent (args);
+ Application.Refresh ();
+ Assert.Equal (tab1, clicked);
+ Assert.Equal (tab1, tv.SelectedTab);
// Click to tab2
- tabRow.MouseEvent(new MouseEvent{
- X = 7,
- Y = 1,
- Flags = MouseFlags.Button1Clicked
+ args = new MouseEventEventArgs (new MouseEvent {
+ X = 6,
+ Y = 1,
+ Flags = MouseFlags.Button1Clicked
});
-
- Assert.Equal(tab2, clicked);
- Assert.Equal(tab2, tv.SelectedTab);
+ Application.OnMouseEvent (args);
+ Application.Refresh ();
+ Assert.Equal (tab2, clicked);
+ Assert.Equal (tab2, tv.SelectedTab);
// cancel navigation
- tv.TabClicked += (s,e)=>{
+ tv.TabClicked += (s, e) => {
clicked = e.Tab;
e.MouseEvent.Handled = true;
};
- tabRow.MouseEvent(new MouseEvent{
- X = 3,
- Y = 1,
- Flags = MouseFlags.Button1Clicked
- });
-
- // Tab 1 was clicked but event handler blocked navigation
- Assert.Equal(tab1, clicked);
- Assert.Equal(tab2, tv.SelectedTab);
-
- tabRow.MouseEvent (new MouseEvent {
- X = 10,
+ args = new MouseEventEventArgs (new MouseEvent {
+ X = 3,
Y = 1,
Flags = MouseFlags.Button1Clicked
});
+ Application.OnMouseEvent (args);
+ Application.Refresh ();
+ // Tab 1 was clicked but event handler blocked navigation
+ Assert.Equal (tab1, clicked);
+ Assert.Equal (tab2, tv.SelectedTab);
+ args = new MouseEventEventArgs (new MouseEvent {
+ X = 12,
+ Y = 1,
+ Flags = MouseFlags.Button1Clicked
+ });
+ Application.OnMouseEvent (args);
+ Application.Refresh ();
// Clicking beyond last tab should raise event with null Tab
Assert.Null (clicked);
Assert.Equal (tab2, tv.SelectedTab);
+ }
+ [Fact, AutoInitShutdown]
+ public void MouseClick_Right_Left_Arrows_ChangesTab ()
+ {
+ var tv = GetTabView (out var tab1, out var tab2, false);
+
+ tv.Width = 7;
+ tv.Height = 5;
+
+ tv.LayoutSubviews ();
+
+ tv.Draw ();
+
+ var tabRow = tv.Subviews [0];
+ Assert.Equal ("TabRowView", tabRow.GetType ().Name);
+
+ TestHelpers.AssertDriverContentsAre (@"
+╭────╮
+│Tab1│
+│ ╰►
+│hi │
+└─────┘
+", output);
+
+ Tab clicked = null;
+
+ tv.TabClicked += (s, e) => {
+ clicked = e.Tab;
+ };
+
+ Tab oldChanged = null;
+ Tab newChanged = null;
+
+ tv.SelectedTabChanged += (s, e) => {
+ oldChanged = e.OldTab;
+ newChanged = e.NewTab;
+ };
+
+ Application.Top.Add (tv);
+ Application.Begin (Application.Top);
+
+ // Click the right arrow
+ var args = new MouseEventEventArgs (new MouseEvent {
+ X = 6,
+ Y = 2,
+ Flags = MouseFlags.Button1Clicked
+ });
+ Application.OnMouseEvent (args);
+ Application.Refresh ();
+ Assert.Null (clicked);
+ Assert.Equal (tab1, oldChanged);
+ Assert.Equal (tab2, newChanged);
+ Assert.Equal (tab2, tv.SelectedTab);
+ TestHelpers.AssertDriverContentsAre (@"
+╭────╮
+│Tab2│
+◄ ╰╮
+│hi2 │
+└─────┘
+", output);
+
+ // Click the left arrow
+ args = new MouseEventEventArgs (new MouseEvent {
+ X = 0,
+ Y = 2,
+ Flags = MouseFlags.Button1Clicked
+ });
+ Application.OnMouseEvent (args);
+ Application.Refresh ();
+ Assert.Null (clicked);
+ Assert.Equal (tab2, oldChanged);
+ Assert.Equal (tab1, newChanged);
+ Assert.Equal (tab1, tv.SelectedTab);
+ TestHelpers.AssertDriverContentsAre (@"
+╭────╮
+│Tab1│
+│ ╰►
+│hi │
+└─────┘
+", output);
+ }
+
+ [Fact, AutoInitShutdown]
+ public void MouseClick_Right_Left_Arrows_ChangesTab_With_Border ()
+ {
+ var tv = GetTabView (out var tab1, out var tab2, false);
+
+ tv.Width = 9;
+ tv.Height = 7;
+
+ Assert.Equal (LineStyle.None, tv.BorderStyle);
+ tv.BorderStyle = LineStyle.Single;
+
+ tv.LayoutSubviews ();
+
+ tv.Draw ();
+
+ var tabRow = tv.Subviews [0];
+ Assert.Equal ("TabRowView", tabRow.GetType ().Name);
+
+ TestHelpers.AssertDriverContentsAre (@"
+┌───────┐
+│╭────╮ │
+││Tab1│ │
+││ ╰►│
+││hi ││
+│└─────┘│
+└───────┘
+", output);
+
+ Tab clicked = null;
+
+ tv.TabClicked += (s, e) => {
+ clicked = e.Tab;
+ };
+
+ Tab oldChanged = null;
+ Tab newChanged = null;
+
+ tv.SelectedTabChanged += (s, e) => {
+ oldChanged = e.OldTab;
+ newChanged = e.NewTab;
+ };
+
+ Application.Top.Add (tv);
+ Application.Begin (Application.Top);
+
+ // Click the right arrow
+ var args = new MouseEventEventArgs (new MouseEvent {
+ X = 7,
+ Y = 3,
+ Flags = MouseFlags.Button1Clicked
+ });
+ Application.OnMouseEvent (args);
+ Application.Refresh ();
+ Assert.Null (clicked);
+ Assert.Equal (tab1, oldChanged);
+ Assert.Equal (tab2, newChanged);
+ Assert.Equal (tab2, tv.SelectedTab);
+ TestHelpers.AssertDriverContentsAre (@"
+┌───────┐
+│╭────╮ │
+││Tab2│ │
+│◄ ╰╮│
+││hi2 ││
+│└─────┘│
+└───────┘
+", output);
+
+ // Click the left arrow
+ args = new MouseEventEventArgs (new MouseEvent {
+ X = 1,
+ Y = 3,
+ Flags = MouseFlags.Button1Clicked
+ });
+ Application.OnMouseEvent (args);
+ Application.Refresh ();
+ Assert.Null (clicked);
+ Assert.Equal (tab2, oldChanged);
+ Assert.Equal (tab1, newChanged);
+ Assert.Equal (tab1, tv.SelectedTab);
+ TestHelpers.AssertDriverContentsAre (@"
+┌───────┐
+│╭────╮ │
+││Tab1│ │
+││ ╰►│
+││hi ││
+│└─────┘│
+└───────┘
+", output);
+ }
+
+ [Fact]
+ public void EnsureValidScrollOffsets_TabScrollOffset ()
+ {
+ var tv = GetTabView (out var tab1, out var tab2);
+
+ // Make tab width small to force only one tab visible at once
+ tv.Width = 4;
+
+ tv.SelectedTab = tab1;
+ Assert.Equal (0, tv.TabScrollOffset);
+
+ tv.TabScrollOffset = 10;
+ tv.SelectedTab = tab2;
+ Assert.Equal (1, tv.TabScrollOffset);
+
+ tv.TabScrollOffset = -1;
+ tv.SelectedTab = tab1;
+ Assert.Equal (0, tv.TabScrollOffset);
+
+ // Shutdown must be called to safely clean up Application if Init has been called
+ Application.Shutdown ();
+ }
+
+ [Fact, AutoInitShutdown]
+ public void ProcessKey_Down_Up_Right_Left_Home_End_PageDown_PageUp ()
+ {
+ var tv = GetTabView (out var tab1, out var tab2, false);
+
+ tv.Width = 7;
+ tv.Height = 5;
+
+ var btn = new Button ("Ok") { Y = Pos.Bottom (tv) + 1, Width = 7 };
+
+ var top = Application.Top;
+ top.Add (tv, btn);
+ Application.Begin (top);
+
+ // Is the selected tab view hosting focused
+ Assert.Equal (tab1, tv.SelectedTab);
+ Assert.Equal (tv, top.Focused);
+ Assert.Equal (tv.MostFocused, top.Focused.MostFocused);
+ Assert.Equal (tv.SelectedTab.View, top.Focused.MostFocused);
+
+ // Press the cursor up key to focus the selected tab
+ var args = new KeyEventEventArgs (new KeyEvent (Key.CursorUp, new KeyModifiers ()));
+ Application.OnKeyPressed (args);
+ Application.Refresh ();
+ // Is the selected tab focused
+ Assert.Equal (tab1, tv.SelectedTab);
+ Assert.Equal (tv, top.Focused);
+ Assert.Equal (tv.MostFocused, top.Focused.MostFocused);
+
+ Tab oldChanged = null;
+ Tab newChanged = null;
+
+ tv.SelectedTabChanged += (s, e) => {
+ oldChanged = e.OldTab;
+ newChanged = e.NewTab;
+ };
+
+ // Press the cursor right key to select the next tab
+ args = new KeyEventEventArgs (new KeyEvent (Key.CursorRight, new KeyModifiers ()));
+ Application.OnKeyPressed (args);
+ Application.Refresh ();
+ Assert.Equal (tab1, oldChanged);
+ Assert.Equal (tab2, newChanged);
+ Assert.Equal (tab2, tv.SelectedTab);
+ Assert.Equal (tv, top.Focused);
+ Assert.Equal (tv.MostFocused, top.Focused.MostFocused);
+
+ // Press the cursor down key to focused the selected tab view hosting
+ args = new KeyEventEventArgs (new KeyEvent (Key.CursorDown, new KeyModifiers ()));
+ Application.OnKeyPressed (args);
+ Application.Refresh ();
+ Assert.Equal (tab2, tv.SelectedTab);
+ Assert.Equal (tv, top.Focused);
+ Assert.Equal (tv.MostFocused, top.Focused.MostFocused);
+ // The tab view hosting is a label which can't be focused
+ // and the View container is the focused one
+ Assert.Equal (tv.Subviews [1], top.Focused.MostFocused);
+
+ // Press the cursor up key to focus the selected tab
+ args = new KeyEventEventArgs (new KeyEvent (Key.CursorUp, new KeyModifiers ()));
+ Application.OnKeyPressed (args);
+ Application.Refresh ();
+ // Is the selected tab focused
+ Assert.Equal (tab2, tv.SelectedTab);
+ Assert.Equal (tv, top.Focused);
+ Assert.Equal (tv.MostFocused, top.Focused.MostFocused);
+
+ // Press the cursor left key to select the previous tab
+ args = new KeyEventEventArgs (new KeyEvent (Key.CursorLeft, new KeyModifiers ()));
+ Application.OnKeyPressed (args);
+ Application.Refresh ();
+ Assert.Equal (tab2, oldChanged);
+ Assert.Equal (tab1, newChanged);
+ Assert.Equal (tab1, tv.SelectedTab);
+ Assert.Equal (tv, top.Focused);
+ Assert.Equal (tv.MostFocused, top.Focused.MostFocused);
+
+ // Press the end key to select the last tab
+ args = new KeyEventEventArgs (new KeyEvent (Key.End, new KeyModifiers ()));
+ Application.OnKeyPressed (args);
+ Application.Refresh ();
+ Assert.Equal (tab1, oldChanged);
+ Assert.Equal (tab2, newChanged);
+ Assert.Equal (tab2, tv.SelectedTab);
+ Assert.Equal (tv, top.Focused);
+ Assert.Equal (tv.MostFocused, top.Focused.MostFocused);
+
+ // Press the home key to select the first tab
+ args = new KeyEventEventArgs (new KeyEvent (Key.Home, new KeyModifiers ()));
+ Application.OnKeyPressed (args);
+ Application.Refresh ();
+ Assert.Equal (tab2, oldChanged);
+ Assert.Equal (tab1, newChanged);
+ Assert.Equal (tab1, tv.SelectedTab);
+ Assert.Equal (tv, top.Focused);
+ Assert.Equal (tv.MostFocused, top.Focused.MostFocused);
+
+ // Press the page down key to select the next set of tabs
+ args = new KeyEventEventArgs (new KeyEvent (Key.PageDown, new KeyModifiers ()));
+ Application.OnKeyPressed (args);
+ Application.Refresh ();
+ Assert.Equal (tab1, oldChanged);
+ Assert.Equal (tab2, newChanged);
+ Assert.Equal (tab2, tv.SelectedTab);
+ Assert.Equal (tv, top.Focused);
+ Assert.Equal (tv.MostFocused, top.Focused.MostFocused);
+
+ // Press the page up key to select the previous set of tabs
+ args = new KeyEventEventArgs (new KeyEvent (Key.PageUp, new KeyModifiers ()));
+ Application.OnKeyPressed (args);
+ Application.Refresh ();
+ Assert.Equal (tab2, oldChanged);
+ Assert.Equal (tab1, newChanged);
+ Assert.Equal (tab1, tv.SelectedTab);
+ Assert.Equal (tv, top.Focused);
+ Assert.Equal (tv.MostFocused, top.Focused.MostFocused);
}
private void InitFakeDriver ()