mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
Merge branch 'v2_develop' into v2_3841-ConfigManager
This commit is contained in:
@@ -1725,8 +1725,13 @@ internal class WindowsDriver : ConsoleDriver
|
||||
}
|
||||
}
|
||||
|
||||
// Return the Key (not KeyChar!)
|
||||
return (KeyCode)keyInfo.Key;
|
||||
// If KeyInfo.Key is A...Z and KeyInfo.KeyChar is not a...z then we have an accent.
|
||||
// See https://github.com/gui-cs/Terminal.Gui/issues/3807#issuecomment-2455997595
|
||||
if (keyInfo.KeyChar <= (char)'z')
|
||||
{
|
||||
return (KeyCode)keyInfo.Key;
|
||||
}
|
||||
return (KeyCode)keyInfo.KeyChar;
|
||||
}
|
||||
|
||||
// Handle control keys whose VK codes match the related ASCII value (those below ASCII 33) like ESC
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,7 @@ public class Tab : View
|
||||
set
|
||||
{
|
||||
_displayText = value;
|
||||
SetNeedsDraw ();
|
||||
SetNeedsLayout ();
|
||||
}
|
||||
}
|
||||
|
||||
793
Terminal.Gui/Views/TabView/TabRowView.cs
Normal file
793
Terminal.Gui/Views/TabView/TabRowView.cs
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnSubviewLayout (LayoutEventArgs args)
|
||||
{
|
||||
_host._tabLocations = _host.CalculateViewport (Viewport).ToArray ();
|
||||
|
||||
RenderTabLine ();
|
||||
|
||||
RenderUnderline ();
|
||||
|
||||
base.OnSubviewLayout (args);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>Renders the line with the tab names in it.</summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Renders the line of the tab that adjoins the content of the tab.</summary>
|
||||
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 (); }
|
||||
}
|
||||
585
Terminal.Gui/Views/TabView/TabView.cs
Normal file
585
Terminal.Gui/Views/TabView/TabView.cs
Normal file
@@ -0,0 +1,585 @@
|
||||
#nullable enable
|
||||
namespace Terminal.Gui;
|
||||
|
||||
/// <summary>Control that hosts multiple sub views, presenting a single one at once.</summary>
|
||||
public class TabView : View
|
||||
{
|
||||
/// <summary>The default <see cref="MaxTabTextWidth"/> to set on new <see cref="TabView"/> controls.</summary>
|
||||
public const uint DefaultMaxTabTextWidth = 30;
|
||||
|
||||
/// <summary>
|
||||
/// This sub view is the main client area of the current tab. It hosts the <see cref="Tab.View"/> of the tab, the
|
||||
/// <see cref="SelectedTab"/>.
|
||||
/// </summary>
|
||||
private readonly View _containerView;
|
||||
|
||||
private readonly List<Tab> _tabs = new ();
|
||||
|
||||
/// <summary>This sub view is the 2 or 3 line control that represents the actual tabs themselves.</summary>
|
||||
private readonly TabRowView _tabsBar;
|
||||
|
||||
private Tab? _selectedTab;
|
||||
|
||||
internal Tab []? _tabLocations;
|
||||
private int _tabScrollOffset;
|
||||
|
||||
/// <summary>Initializes a <see cref="TabView"/> class.</summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The maximum number of characters to render in a Tab header. This prevents one long tab from pushing out all
|
||||
/// the others.
|
||||
/// </summary>
|
||||
public uint MaxTabTextWidth { get; set; } = DefaultMaxTabTextWidth;
|
||||
|
||||
// This is needed to hold initial value because it may change during the setter process
|
||||
private bool _selectedTabHasFocus;
|
||||
|
||||
/// <summary>The currently selected member of <see cref="Tabs"/> chosen by the user.</summary>
|
||||
/// <value></value>
|
||||
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 ();
|
||||
|
||||
/// <summary>Render choices for how to display tabs. After making changes, call <see cref="ApplyStyleChanges()"/>.</summary>
|
||||
/// <value></value>
|
||||
public TabStyle Style
|
||||
{
|
||||
get => _style;
|
||||
set
|
||||
{
|
||||
if (_style == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_style = value;
|
||||
SetNeedsLayout ();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>All tabs currently hosted by the control.</summary>
|
||||
/// <value></value>
|
||||
public IReadOnlyCollection<Tab> Tabs => _tabs.AsReadOnly ();
|
||||
|
||||
/// <summary>When there are too many tabs to render, this indicates the first tab to render on the screen.</summary>
|
||||
/// <value></value>
|
||||
public int TabScrollOffset
|
||||
{
|
||||
get => _tabScrollOffset;
|
||||
set
|
||||
{
|
||||
_tabScrollOffset = EnsureValidScrollOffsets (value);
|
||||
SetNeedsLayout ();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Adds the given <paramref name="tab"/> to <see cref="Tabs"/>.</summary>
|
||||
/// <param name="tab"></param>
|
||||
/// <param name="andSelect">True to make the newly added Tab the <see cref="SelectedTab"/>.</param>
|
||||
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 ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the control to use the latest state settings in <see cref="Style"/>. This can change the size of the
|
||||
/// client area of the tab (for rendering the selected tab's content). This method includes a call to
|
||||
/// <see cref="View.SetNeedsDraw()"/>.
|
||||
/// </summary>
|
||||
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 ();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnViewportChanged (DrawEventArgs e)
|
||||
{
|
||||
_tabLocations = CalculateViewport (Viewport).ToArray ();
|
||||
|
||||
base.OnViewportChanged (e);
|
||||
}
|
||||
|
||||
/// <summary>Updates <see cref="TabScrollOffset"/> to ensure that <see cref="SelectedTab"/> is visible.</summary>
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Updates <see cref="TabScrollOffset"/> to be a valid index of <see cref="Tabs"/>.</summary>
|
||||
/// <param name="value">The value to validate.</param>
|
||||
/// <remarks>Changes will not be immediately visible in the display until you call <see cref="View.SetNeedsDraw()"/>.</remarks>
|
||||
/// <returns>The valid <see cref="TabScrollOffset"/> for the given value.</returns>
|
||||
public int EnsureValidScrollOffsets (int value) { return Math.Max (Math.Min (value, Tabs.Count - 1), 0); }
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the given <paramref name="tab"/> from <see cref="Tabs"/>. Caller is responsible for disposing the
|
||||
/// tab's hosted <see cref="Tab.View"/> if appropriate.
|
||||
/// </summary>
|
||||
/// <param name="tab"></param>
|
||||
public void RemoveTab (Tab? tab)
|
||||
{
|
||||
if (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 ();
|
||||
}
|
||||
|
||||
/// <summary>Event for when <see cref="SelectedTab"/> changes.</summary>
|
||||
public event EventHandler<TabChangedEventArgs>? SelectedTabChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Changes the <see cref="SelectedTab"/> by the given <paramref name="amount"/>. Positive for right, negative for
|
||||
/// left. If no tab is currently selected then the first tab will become selected.
|
||||
/// </summary>
|
||||
/// <param name="amount"></param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when a <see cref="Tab"/> is clicked. Can be used to cancel navigation, show context menu (e.g. on
|
||||
/// right click) etc.
|
||||
/// </summary>
|
||||
public event EventHandler<TabMouseEventArgs>? TabClicked;
|
||||
|
||||
/// <summary>Disposes the control and all <see cref="Tabs"/>.</summary>
|
||||
/// <param name="disposing"></param>
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
base.Dispose (disposing);
|
||||
|
||||
// The selected tab will automatically be disposed but
|
||||
// any tabs not visible will need to be manually disposed
|
||||
|
||||
foreach (Tab tab in Tabs)
|
||||
{
|
||||
if (!Equals (SelectedTab, tab))
|
||||
{
|
||||
tab.View?.Dispose ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Raises the <see cref="SelectedTabChanged"/> event.</summary>
|
||||
protected virtual void OnSelectedTabChanged (Tab oldTab, Tab newTab)
|
||||
{
|
||||
SelectedTabChanged?.Invoke (this, new TabChangedEventArgs (oldTab, newTab));
|
||||
}
|
||||
|
||||
/// <summary>Returns which tabs to render at each x location.</summary>
|
||||
/// <returns></returns>
|
||||
internal IEnumerable<Tab> 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 ();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of rows occupied by rendering the tabs, this depends on <see cref="TabStyle.ShowTopLine"/>
|
||||
/// and can be 0 (e.g. if <see cref="TabStyle.TabsOnBottom"/> and you ask for <paramref name="top"/>).
|
||||
/// </summary>
|
||||
/// <param name="top">True to measure the space required at the top of the control, false to measure space at the bottom.</param>
|
||||
/// .
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Raises the <see cref="TabClicked"/> event.</summary>
|
||||
/// <param name="tabMouseEventArgs"></param>
|
||||
internal virtual void OnTabClicked (TabMouseEventArgs tabMouseEventArgs) { TabClicked?.Invoke (this, tabMouseEventArgs); }
|
||||
|
||||
|
||||
}
|
||||
@@ -197,6 +197,11 @@ public class SetupFakeDriverAttribute : BeforeAfterTestAttribute
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -812,6 +812,4 @@ public class SetLayoutTests (ITestOutputHelper output)
|
||||
Assert.Equal (19, v2.Frame.Height);
|
||||
t.Dispose ();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using Xunit.Abstractions;
|
||||
|
||||
namespace Terminal.Gui.ViewsTests;
|
||||
|
||||
#if foo
|
||||
public class TabViewTests (ITestOutputHelper output)
|
||||
{
|
||||
[Fact]
|
||||
@@ -113,8 +112,6 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
tv.Width = 20;
|
||||
tv.Height = 5;
|
||||
|
||||
tv.Layout ();
|
||||
|
||||
tv.Draw ();
|
||||
|
||||
View tabRow = tv.Subviews [0];
|
||||
@@ -146,21 +143,21 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
{
|
||||
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 class TabViewTests (ITestOutputHelper output)
|
||||
|
||||
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 class TabViewTests (ITestOutputHelper output)
|
||||
|
||||
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 class TabViewTests (ITestOutputHelper output)
|
||||
tv.Width = 7;
|
||||
tv.Height = 5;
|
||||
|
||||
tv.LayoutSubviews ();
|
||||
|
||||
tv.Draw ();
|
||||
|
||||
View tabRow = tv.Subviews [0];
|
||||
@@ -236,7 +231,7 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
// 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 class TabViewTests (ITestOutputHelper output)
|
||||
// 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 class TabViewTests (ITestOutputHelper output)
|
||||
|
||||
Assert.Equal (LineStyle.None, tv.BorderStyle);
|
||||
tv.BorderStyle = LineStyle.Single;
|
||||
|
||||
tv.LayoutSubviews ();
|
||||
tv.Layout ();
|
||||
|
||||
tv.Draw ();
|
||||
|
||||
@@ -327,7 +321,7 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
// 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 class TabViewTests (ITestOutputHelper output)
|
||||
// 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 class TabViewTests (ITestOutputHelper output)
|
||||
|
||||
// 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 class TabViewTests (ITestOutputHelper output)
|
||||
|
||||
// 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 class TabViewTests (ITestOutputHelper output)
|
||||
|
||||
// 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 class TabViewTests (ITestOutputHelper output)
|
||||
|
||||
// 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 class TabViewTests (ITestOutputHelper output)
|
||||
|
||||
// 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 class TabViewTests (ITestOutputHelper output)
|
||||
|
||||
// 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 class TabViewTests (ITestOutputHelper output)
|
||||
|
||||
// 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 class TabViewTests (ITestOutputHelper output)
|
||||
tv.ApplyStyleChanges ();
|
||||
tv.Layout ();
|
||||
|
||||
View.ClipToScreen ();
|
||||
tv.Draw ();
|
||||
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (
|
||||
@@ -633,7 +626,7 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
tv.Height = 5;
|
||||
tv.Style = new () { ShowTopLine = false };
|
||||
tv.ApplyStyleChanges ();
|
||||
tv.LayoutSubviews ();
|
||||
tv.Layout ();
|
||||
|
||||
tv.Draw ();
|
||||
|
||||
@@ -658,13 +651,13 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
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 class TabViewTests (ITestOutputHelper output)
|
||||
);
|
||||
|
||||
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 class TabViewTests (ITestOutputHelper output)
|
||||
// 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 class TabViewTests (ITestOutputHelper output)
|
||||
|
||||
//switch to tab2
|
||||
tv.SelectedTab = tab2;
|
||||
View.ClipToScreen ();
|
||||
tv.Draw ();
|
||||
|
||||
tv.Layout ();
|
||||
View.SetClipToScreen ();
|
||||
tv.Draw ();
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (
|
||||
@"
|
||||
│13│
|
||||
@@ -730,9 +726,9 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
tab1.DisplayText = "12345678910";
|
||||
tab2.DisplayText = "abcdefghijklmnopq";
|
||||
|
||||
View.ClipToScreen ();
|
||||
tv.Layout ();
|
||||
View.SetClipToScreen ();
|
||||
tv.Draw ();
|
||||
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (
|
||||
@"
|
||||
│abcdefg│
|
||||
@@ -753,9 +749,8 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
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 class TabViewTests (ITestOutputHelper output)
|
||||
tv.Height = 5;
|
||||
tv.Style = new () { ShowTopLine = false, TabsOnBottom = true };
|
||||
tv.ApplyStyleChanges ();
|
||||
tv.LayoutSubviews ();
|
||||
tv.Layout ();
|
||||
|
||||
tv.Draw ();
|
||||
|
||||
@@ -802,15 +797,13 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
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 class TabViewTests (ITestOutputHelper output)
|
||||
);
|
||||
|
||||
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 class TabViewTests (ITestOutputHelper output)
|
||||
tab1.DisplayText = "12345678910";
|
||||
tab2.DisplayText = "13";
|
||||
|
||||
View.ClipToScreen ();
|
||||
tv.Layout ();
|
||||
View.SetClipToScreen ();
|
||||
tv.Draw ();
|
||||
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (
|
||||
@@ -859,7 +855,9 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
|
||||
//switch to tab2
|
||||
tv.SelectedTab = tab2;
|
||||
View.ClipToScreen ();
|
||||
|
||||
tv.Layout ();
|
||||
View.SetClipToScreen ();
|
||||
tv.Draw ();
|
||||
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (
|
||||
@@ -876,7 +874,8 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
tab1.DisplayText = "12345678910";
|
||||
tab2.DisplayText = "abcdefghijklmnopq";
|
||||
|
||||
View.ClipToScreen ();
|
||||
tv.Layout ();
|
||||
View.SetClipToScreen ();
|
||||
tv.Draw ();
|
||||
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (
|
||||
@@ -899,7 +898,6 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
tv.Height = 5;
|
||||
tv.Layout ();
|
||||
|
||||
View.ClipToScreen ();
|
||||
tv.Draw ();
|
||||
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (
|
||||
@@ -922,7 +920,7 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
tv.Height = 5;
|
||||
tv.Layout ();
|
||||
|
||||
View.ClipToScreen ();
|
||||
View.SetClipToScreen ();
|
||||
tv.Draw ();
|
||||
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (
|
||||
@@ -944,14 +942,11 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
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 class TabViewTests (ITestOutputHelper output)
|
||||
|
||||
tv.SelectedTab = tab2;
|
||||
|
||||
View.ClipToScreen ();
|
||||
tv.Layout ();
|
||||
View.SetClipToScreen ();
|
||||
tv.Draw ();
|
||||
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (
|
||||
@@ -985,7 +981,8 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
tab1.DisplayText = "12345678910";
|
||||
tab2.DisplayText = "13";
|
||||
|
||||
View.ClipToScreen ();
|
||||
tv.Layout ();
|
||||
View.SetClipToScreen ();
|
||||
tv.Draw ();
|
||||
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (
|
||||
@@ -1000,7 +997,9 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
|
||||
//switch to tab2
|
||||
tv.SelectedTab = tab2;
|
||||
View.ClipToScreen ();
|
||||
|
||||
tv.Layout ();
|
||||
View.SetClipToScreen ();
|
||||
tv.Draw ();
|
||||
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (
|
||||
@@ -1017,7 +1016,8 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
tab1.DisplayText = "12345678910";
|
||||
tab2.DisplayText = "abcdefghijklmnopq";
|
||||
|
||||
View.ClipToScreen ();
|
||||
tv.Layout ();
|
||||
View.SetClipToScreen ();
|
||||
tv.Draw ();
|
||||
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (
|
||||
@@ -1039,13 +1039,11 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
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 class TabViewTests (ITestOutputHelper output)
|
||||
|
||||
tv.SelectedTab = tab2;
|
||||
|
||||
View.ClipToScreen ();
|
||||
tv.Layout ();
|
||||
View.SetClipToScreen ();
|
||||
tv.Draw ();
|
||||
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (
|
||||
@@ -1083,7 +1082,7 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
tv.Height = 5;
|
||||
tv.Style = new () { TabsOnBottom = true };
|
||||
tv.ApplyStyleChanges ();
|
||||
tv.LayoutSubviews ();
|
||||
tv.Layout ();
|
||||
|
||||
tv.Draw ();
|
||||
|
||||
@@ -1107,7 +1106,7 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
tv.Height = 5;
|
||||
tv.Style = new () { TabsOnBottom = true };
|
||||
tv.ApplyStyleChanges ();
|
||||
tv.LayoutSubviews ();
|
||||
tv.Layout ();
|
||||
|
||||
tv.Draw ();
|
||||
|
||||
@@ -1131,15 +1130,13 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
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 class TabViewTests (ITestOutputHelper output)
|
||||
tab1.DisplayText = "12345678910";
|
||||
tab2.DisplayText = "13";
|
||||
|
||||
View.ClipToScreen ();
|
||||
tv.Layout ();
|
||||
View.SetClipToScreen ();
|
||||
tv.Draw ();
|
||||
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (
|
||||
@@ -1171,7 +1169,9 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
|
||||
//switch to tab2
|
||||
tv.SelectedTab = tab2;
|
||||
View.ClipToScreen ();
|
||||
|
||||
tv.Layout ();
|
||||
View.SetClipToScreen ();
|
||||
tv.Draw ();
|
||||
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (
|
||||
@@ -1188,7 +1188,8 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
tab1.DisplayText = "12345678910";
|
||||
tab2.DisplayText = "abcdefghijklmnopq";
|
||||
|
||||
View.ClipToScreen ();
|
||||
tv.Layout ();
|
||||
View.SetClipToScreen ();
|
||||
tv.Draw ();
|
||||
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (
|
||||
@@ -1212,12 +1213,11 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
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 class TabViewTests (ITestOutputHelper output)
|
||||
|
||||
tv.SelectedTab = tab2;
|
||||
|
||||
View.ClipToScreen ();
|
||||
tv.Layout ();
|
||||
View.SetClipToScreen ();
|
||||
tv.Draw ();
|
||||
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (
|
||||
@@ -1322,6 +1323,138 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
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 @@ public class TabViewTests (ITestOutputHelper output)
|
||||
driver.Init ();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user