#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 (); }
}