diff --git a/Terminal.Gui/View/View.Navigation.cs b/Terminal.Gui/View/View.Navigation.cs
index e68b3c7d3..094ed5a01 100644
--- a/Terminal.Gui/View/View.Navigation.cs
+++ b/Terminal.Gui/View/View.Navigation.cs
@@ -850,18 +850,18 @@ public partial class View // Focus and cross-view navigation management (TabStop
SetNeedsDisplay ();
}
- private void RaiseFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedVew)
+ private void RaiseFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedView)
{
- if (newHasFocus && focusedVew?.Focused is null)
+ if (newHasFocus && focusedView?.Focused is null)
{
- Application.Navigation?.SetFocused (focusedVew);
+ Application.Navigation?.SetFocused (focusedView);
}
// Call the virtual method
- OnHasFocusChanged (newHasFocus, previousFocusedView, focusedVew);
+ OnHasFocusChanged (newHasFocus, previousFocusedView, focusedView);
// Raise the event
- var args = new HasFocusEventArgs (newHasFocus, newHasFocus, previousFocusedView, focusedVew);
+ var args = new HasFocusEventArgs (newHasFocus, newHasFocus, previousFocusedView, focusedView);
HasFocusChanged?.Invoke (this, args);
}
@@ -876,8 +876,8 @@ public partial class View // Focus and cross-view navigation management (TabStop
///
/// The new value of .
///
- /// The view that is now focused. May be
- protected virtual void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedVew) { }
+ /// The view that is now focused. May be
+ protected virtual void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedView) { }
/// Raised after has changed.
///
diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs
index fbaaf4b23..9005a7ecd 100644
--- a/Terminal.Gui/Views/ComboBox.cs
+++ b/Terminal.Gui/Views/ComboBox.cs
@@ -962,7 +962,7 @@ public class ComboBox : View, IDesignable
return true;
}
- protected override void OnHasFocusChanged (bool newHasFocus, [CanBeNull] View previousFocusedView, [CanBeNull] View focusedVew)
+ protected override void OnHasFocusChanged (bool newHasFocus, [CanBeNull] View previousFocusedView, [CanBeNull] View focusedView)
{
if (newHasFocus)
{
diff --git a/Terminal.Gui/Views/Tab.cs b/Terminal.Gui/Views/Tab.cs
index eb42a59b9..be493ecf0 100644
--- a/Terminal.Gui/Views/Tab.cs
+++ b/Terminal.Gui/Views/Tab.cs
@@ -1,9 +1,10 @@
-namespace Terminal.Gui;
+#nullable enable
+namespace Terminal.Gui;
/// A single tab in a .
public class Tab : View
{
- private string _displayText;
+ private string? _displayText;
/// Creates a new unnamed tab with no controls inside.
public Tab ()
@@ -27,5 +28,5 @@ public class Tab : View
/// The control to display when the tab is selected.
///
- public View View { get; set; }
+ public View? View { get; set; }
}
diff --git a/Terminal.Gui/Views/TabView.cs b/Terminal.Gui/Views/TabView.cs
index 5ef35e5c4..82231d918 100644
--- a/Terminal.Gui/Views/TabView.cs
+++ b/Terminal.Gui/Views/TabView.cs
@@ -1,5 +1,4 @@
-using System.Diagnostics;
-
+#nullable enable
namespace Terminal.Gui;
/// Control that hosts multiple sub views, presenting a single one at once.
@@ -19,8 +18,8 @@ public class TabView : View
/// This sub view is the 2 or 3 line control that represents the actual tabs themselves.
private readonly TabRowView _tabsBar;
- private Tab _selectedTab;
- private TabToRender [] _tabLocations;
+ private Tab? _selectedTab;
+ private TabToRender []? _tabLocations;
private int _tabScrollOffset;
/// Initializes a class.
@@ -48,7 +47,7 @@ public class TabView : View
() =>
{
TabScrollOffset = 0;
- SelectedTab = Tabs.FirstOrDefault ();
+ SelectedTab = Tabs.FirstOrDefault ()!;
return true;
}
@@ -59,7 +58,7 @@ public class TabView : View
() =>
{
TabScrollOffset = Tabs.Count - 1;
- SelectedTab = Tabs.LastOrDefault ();
+ SelectedTab = Tabs.LastOrDefault()!;
return true;
}
@@ -69,7 +68,7 @@ public class TabView : View
Command.PageDown,
() =>
{
- TabScrollOffset += _tabLocations.Length;
+ TabScrollOffset += _tabLocations!.Length;
SelectedTab = Tabs.ElementAt (TabScrollOffset);
return true;
@@ -80,7 +79,7 @@ public class TabView : View
Command.PageUp,
() =>
{
- TabScrollOffset -= _tabLocations.Length;
+ TabScrollOffset -= _tabLocations!.Length;
SelectedTab = Tabs.ElementAt (TabScrollOffset);
return true;
@@ -104,19 +103,20 @@ public class TabView : View
/// The currently selected member of chosen by the user.
///
- public Tab SelectedTab
+ public Tab? SelectedTab
{
get => _selectedTab;
set
{
UnSetCurrentTabs ();
- Tab old = _selectedTab;
+ Tab? old = _selectedTab;
if (_selectedTab is { })
{
if (_selectedTab.View is { })
{
+ _selectedTab.View.CanFocusChanged -= ContentViewCanFocus!;
// remove old content
_contentView.Remove (_selectedTab.View);
}
@@ -124,33 +124,36 @@ public class TabView : View
_selectedTab = value;
- if (value is { })
+ // add new content
+ if (_selectedTab?.View != null)
{
- // add new content
- if (_selectedTab.View is { })
- {
- _contentView.Add (_selectedTab.View);
- // _contentView.Id = $"_contentView for {_selectedTab.DisplayText}";
- }
+ _selectedTab.View.CanFocusChanged += ContentViewCanFocus!;
+ _contentView.Add (_selectedTab.View);
+ // _contentView.Id = $"_contentView for {_selectedTab.DisplayText}";
}
- _contentView.CanFocus = _contentView.Subviews.Count (v => v.CanFocus) > 0;
+ ContentViewCanFocus (null!, null!);
EnsureSelectedTabIsVisible ();
- if (old != value)
+ if (old != _selectedTab)
{
if (old?.HasFocus == true)
{
SelectedTab?.SetFocus ();
}
- OnSelectedTabChanged (old, value);
+ OnSelectedTabChanged (old!, _selectedTab!);
}
SetNeedsLayout ();
}
}
+ private void ContentViewCanFocus (object sender, EventArgs eventArgs)
+ {
+ _contentView.CanFocus = _contentView.Subviews.Count (v => v.CanFocus) > 0;
+ }
+
private TabStyle _style = new ();
/// Render choices for how to display tabs. After making changes, call .
@@ -289,6 +292,19 @@ public class TabView : View
/// The valid for the given value.
public int EnsureValidScrollOffsets (int value) { return Math.Max (Math.Min (value, Tabs.Count - 1), 0); }
+ ///
+ protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedView)
+ {
+ if (SelectedTab is { } && !_contentView.CanFocus && focusedView == this)
+ {
+ SelectedTab?.SetFocus ();
+
+ return;
+ }
+
+ base.OnHasFocusChanged (newHasFocus, previousFocusedView, focusedView);
+ }
+
///
protected override bool OnDrawingContent (Rectangle viewport)
{
@@ -313,7 +329,7 @@ public class TabView : View
/// tab's hosted if appropriate.
///
///
- public void RemoveTab (Tab tab)
+ public void RemoveTab (Tab? tab)
{
if (tab is null || !_tabs.Contains (tab))
{
@@ -346,7 +362,7 @@ public class TabView : View
}
/// Event for when changes.
- public event EventHandler SelectedTabChanged;
+ public event EventHandler? SelectedTabChanged;
///
/// Changes the by the given . Positive for right, negative for
@@ -395,7 +411,7 @@ public class TabView : View
/// Event fired when a is clicked. Can be used to cancel navigation, show context menu (e.g. on
/// right click) etc.
///
- public event EventHandler TabClicked;
+ public event EventHandler? TabClicked;
/// Disposes the control and all .
///
@@ -428,7 +444,7 @@ public class TabView : View
UnSetCurrentTabs ();
var i = 1;
- View prevTab = null;
+ View? prevTab = null;
// Starting at the first or scrolled to tab
foreach (Tab tab in Tabs.Skip (TabScrollOffset))
@@ -463,9 +479,9 @@ public class TabView : View
if (maxWidth == 0)
{
tab.Visible = true;
- tab.MouseClick += Tab_MouseClick;
+ tab.MouseClick += Tab_MouseClick!;
- yield return new TabToRender (i, tab, string.Empty, Equals (SelectedTab, tab), 0);
+ yield return new TabToRender (tab, string.Empty, Equals (SelectedTab, tab));
break;
}
@@ -489,9 +505,9 @@ public class TabView : View
// there is enough space!
tab.Visible = true;
- tab.MouseClick += Tab_MouseClick;
+ tab.MouseClick += Tab_MouseClick!;
- yield return new TabToRender (i, tab, text, Equals (SelectedTab, tab), tabTextWidth);
+ yield return new TabToRender (tab, text, Equals (SelectedTab, tab));
i += tabTextWidth + 1;
}
@@ -530,7 +546,7 @@ public class TabView : View
{
foreach (TabToRender tabToRender in _tabLocations)
{
- tabToRender.Tab.MouseClick -= Tab_MouseClick;
+ tabToRender.Tab.MouseClick -= Tab_MouseClick!;
tabToRender.Tab.Visible = false;
}
@@ -565,7 +581,7 @@ public class TabView : View
Visible = false,
Text = Glyphs.RightArrow.ToString ()
};
- _rightScrollIndicator.MouseClick += _host.Tab_MouseClick;
+ _rightScrollIndicator.MouseClick += _host.Tab_MouseClick!;
_leftScrollIndicator = new View
{
@@ -575,14 +591,14 @@ public class TabView : View
Visible = false,
Text = Glyphs.LeftArrow.ToString ()
};
- _leftScrollIndicator.MouseClick += _host.Tab_MouseClick;
+ _leftScrollIndicator.MouseClick += _host.Tab_MouseClick!;
Add (_rightScrollIndicator, _leftScrollIndicator);
}
protected override bool OnMouseEvent (MouseEventArgs me)
{
- Tab hit = me.View is Tab ? (Tab)me.View : null;
+ Tab? hit = me.View as Tab;
if (me.IsSingleClicked)
{
@@ -652,14 +668,20 @@ public class TabView : View
{
_host._tabLocations = _host.CalculateViewport (Viewport).ToArray ();
- RenderTabLine ();
- RenderUnderline ();
SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ());
return true;
}
+ ///
+ protected override bool OnDrawingSubviews (Rectangle viewport)
+ {
+ RenderTabLine ();
+
+ return true;
+ }
+
protected override void OnDrawComplete ()
{
if (_host._tabLocations is null)
@@ -1190,10 +1212,10 @@ public class TabView : View
}
tab.LineCanvas.Merge (lc);
- tab.DrawAdornments ();
- }
+ tab.RenderLineCanvas ();
- return;
+ RenderUnderline ();
+ }
}
private int GetUnderlineYPosition ()
@@ -1209,21 +1231,15 @@ public class TabView : View
/// Renders the line with the tab names in it.
private void RenderTabLine ()
{
- TabToRender [] tabLocations = _host._tabLocations;
- int y;
+ TabToRender []? tabLocations = _host._tabLocations;
- if (_host.Style.TabsOnBottom)
+ if (tabLocations is null)
{
- y = 1;
- }
- else
- {
- y = _host.Style.ShowTopLine ? 1 : 0;
+ return;
}
- View selected = null;
+ View? selected = null;
int topLine = _host.Style.ShowTopLine ? 1 : 0;
- int width = Viewport.Width;
foreach (TabToRender toRender in tabLocations)
{
@@ -1257,7 +1273,7 @@ public class TabView : View
tab.Margin.Thickness = new Thickness (0, 0, 0, 0);
}
- tab.Width = Math.Max (tab.Width.GetAnchor (0) - 1, 1);
+ tab.Width = Math.Max (tab.Width!.GetAnchor (0) - 1, 1);
}
else
{
@@ -1272,7 +1288,7 @@ public class TabView : View
tab.Margin.Thickness = new Thickness (0, 0, 0, 0);
}
- tab.Width = Math.Max (tab.Width.GetAnchor (0) - 1, 1);
+ tab.Width = Math.Max (tab.Width!.GetAnchor (0) - 1, 1);
}
tab.Text = toRender.TextToRender;
@@ -1317,7 +1333,7 @@ public class TabView : View
{
int y = GetUnderlineYPosition ();
- TabToRender selected = _host._tabLocations.FirstOrDefault (t => t.IsSelected);
+ TabToRender? selected = _host._tabLocations?.FirstOrDefault (t => t.IsSelected);
if (selected is null)
{
@@ -1363,17 +1379,15 @@ public class TabView : View
}
}
- private bool ShouldDrawRightScrollIndicator () { return _host._tabLocations.LastOrDefault ()?.Tab != _host.Tabs.LastOrDefault (); }
+ private bool ShouldDrawRightScrollIndicator () { return _host._tabLocations!.LastOrDefault ()?.Tab != _host.Tabs.LastOrDefault (); }
}
private class TabToRender
{
- public TabToRender (int x, Tab tab, string textToRender, bool isSelected, int width)
+ public TabToRender (Tab tab, string textToRender, bool isSelected)
{
- X = x;
Tab = tab;
IsSelected = isSelected;
- Width = width;
TextToRender = textToRender;
}
@@ -1383,7 +1397,5 @@ public class TabView : View
public Tab Tab { get; }
public string TextToRender { get; }
- public int Width { get; }
- public int X { get; set; }
}
}
diff --git a/UICatalog/Scenarios/ASCIICustomButton.cs b/UICatalog/Scenarios/ASCIICustomButton.cs
index 45889f3e8..d7e181eae 100644
--- a/UICatalog/Scenarios/ASCIICustomButton.cs
+++ b/UICatalog/Scenarios/ASCIICustomButton.cs
@@ -111,7 +111,7 @@ public class ASCIICustomButtonTest : Scenario
Add (_border, _fill, title);
}
- protected override void OnHasFocusChanged (bool newHasFocus, [CanBeNull] View previousFocusedView, [CanBeNull] View focusedVew)
+ protected override void OnHasFocusChanged (bool newHasFocus, [CanBeNull] View previousFocusedView, [CanBeNull] View focusedView)
{
if (newHasFocus)
{
diff --git a/UnitTests/Views/ScrollViewTests.cs b/UnitTests/Views/ScrollViewTests.cs
index 325a09d34..6122f0e64 100644
--- a/UnitTests/Views/ScrollViewTests.cs
+++ b/UnitTests/Views/ScrollViewTests.cs
@@ -1134,7 +1134,7 @@ public class ScrollViewTests (ITestOutputHelper output)
CanFocus = true;
}
- protected override void OnHasFocusChanged (bool newHasFocus, [CanBeNull] View previousFocusedView, [CanBeNull] View focusedVew)
+ protected override void OnHasFocusChanged (bool newHasFocus, [CanBeNull] View previousFocusedView, [CanBeNull] View focusedView)
{
if (newHasFocus)
{
diff --git a/UnitTests/Views/TabViewTests.cs b/UnitTests/Views/TabViewTests.cs
index c470020e6..dc66cc298 100644
--- a/UnitTests/Views/TabViewTests.cs
+++ b/UnitTests/Views/TabViewTests.cs
@@ -103,7 +103,7 @@ public class TabViewTests (ITestOutputHelper output)
Application.Shutdown ();
}
- [Fact (Skip = "#3789 Broke. The right way to fix is to refactor TabView to separate Layout and Draw")]
+ [Fact]
[AutoInitShutdown]
public void MouseClick_ChangesTab ()
{
@@ -188,7 +188,7 @@ public class TabViewTests (ITestOutputHelper output)
top.Dispose ();
}
- [Fact (Skip = "#3789 Broke. The right way to fix is to refactor TabView to separate Layout and Draw")]
+ [Fact]
[AutoInitShutdown]
public void MouseClick_Right_Left_Arrows_ChangesTab ()
{
@@ -274,7 +274,7 @@ public class TabViewTests (ITestOutputHelper output)
top.Dispose ();
}
- [Fact (Skip = "#3789 Broke. The right way to fix is to refactor TabView to separate Layout and Draw")]
+ [Fact]
[AutoInitShutdown]
public void MouseClick_Right_Left_Arrows_ChangesTab_With_Border ()
{
@@ -369,7 +369,7 @@ public class TabViewTests (ITestOutputHelper output)
top.Dispose ();
}
- [Fact (Skip="#3789 Broke. The right way to fix is to refactor TabView to separate Layout and Draw")]
+ [Fact]
[AutoInitShutdown]
public void ProcessKey_Down_Up_Right_Left_Home_End_PageDown_PageUp ()
{
@@ -429,7 +429,7 @@ public class TabViewTests (ITestOutputHelper output)
Assert.Equal (tab2, tv.SelectedTab);
Assert.Equal (btn, top.MostFocused);
- // Add a focusable subview to Selected Tab
+ // Add a focusable subview to Selected Tab View which is a Label with CanFocus as false
var btnSubView = new View ()
{
Id = "btnSubView",
@@ -438,9 +438,17 @@ public class TabViewTests (ITestOutputHelper output)
};
tv.SelectedTab.View.Add (btnSubView);
+ Assert.False (tv.SelectedTab.View.CanFocus);
+
// Press cursor up. Should focus the subview in the selected tab.
Application.RaiseKeyDownEvent (Key.CursorUp);
Assert.Equal (tab2, tv.SelectedTab);
+ Assert.NotEqual (btnSubView, top.MostFocused);
+ Assert.Equal (tab2, top.MostFocused);
+
+ tv.SelectedTab.View.CanFocus = true;
+ Application.RaiseKeyDownEvent (Key.CursorDown);
+ Assert.Equal (tab2, tv.SelectedTab);
Assert.Equal (btnSubView, top.MostFocused);
Application.RaiseKeyDownEvent (Key.CursorUp);
@@ -451,24 +459,18 @@ public class TabViewTests (ITestOutputHelper output)
Application.RaiseKeyDownEvent (Key.CursorDown);
Assert.Equal (btn, top.MostFocused);
- // Press the cursor down key again will focus next view in the toplevel, whic is the TabView
+ // Press the cursor down key again will focus next view in the toplevel, which is the TabView
Application.RaiseKeyDownEvent (Key.CursorDown);
Assert.Equal (tab2, tv.SelectedTab);
Assert.Equal (tv, top.Focused);
- Assert.Equal (tab1, tv.MostFocused);
+ // Due to the RestoreFocus method prioritize the _previouslyFocused, so btnSubView will be focused again
+ Assert.Equal (btnSubView, tv.MostFocused);
- // Press the cursor down key to focus the selected tab view hosting again
- Application.RaiseKeyDownEvent (Key.CursorDown);
- Assert.Equal (tab2, tv.SelectedTab);
- Assert.Equal (btnSubView, top.MostFocused);
-
- // Press the cursor up key to focus the selected tab
+ // Press the cursor up key to focus the selected tab which it's the only way to do that
Application.RaiseKeyDownEvent (Key.CursorUp);
- Application.Refresh ();
-
- // Is the selected tab focused
Assert.Equal (tab2, tv.SelectedTab);
Assert.Equal (tv, top.Focused);
+ Assert.Equal (tab2, top.Focused.MostFocused);
Assert.Equal (tv.MostFocused, top.Focused.MostFocused);
// Press the cursor left key to select the previous tab
@@ -479,6 +481,7 @@ public class TabViewTests (ITestOutputHelper output)
Assert.Equal (tab1, tv.SelectedTab);
Assert.Equal (tv, top.Focused);
Assert.Equal (tv.MostFocused, top.Focused.MostFocused);
+ Assert.Equal (tab1, top.Focused.MostFocused);
// Press the end key to select the last tab
Application.RaiseKeyDownEvent (Key.End);
@@ -595,7 +598,7 @@ public class TabViewTests (ITestOutputHelper output)
Application.Shutdown ();
}
- [Fact (Skip = "#3789 Broke. The right way to fix is to refactor TabView to separate Layout and Draw")]
+ [Fact]
[SetupFakeDriver]
public void ShowTopLine_False_TabsOnBottom_False_TestTabView_Width3 ()
{
@@ -643,7 +646,7 @@ public class TabViewTests (ITestOutputHelper output)
);
}
- [Fact (Skip = "#3789 Broke. The right way to fix is to refactor TabView to separate Layout and Draw")]
+ [Fact]
[SetupFakeDriver]
public void ShowTopLine_False_TabsOnBottom_False_TestThinTabView_WithLongNames ()
{
@@ -735,7 +738,7 @@ public class TabViewTests (ITestOutputHelper output)
);
}
- [Fact (Skip = "#3789 Broke. The right way to fix is to refactor TabView to separate Layout and Draw")]
+ [Fact]
[SetupFakeDriver]
public void ShowTopLine_False_TabsOnBottom_True_TestTabView_Width3 ()
{
@@ -783,7 +786,7 @@ public class TabViewTests (ITestOutputHelper output)
);
}
- [Fact (Skip = "#3789 Broke. The right way to fix is to refactor TabView to separate Layout and Draw")]
+ [Fact]
[SetupFakeDriver]
public void ShowTopLine_False_TabsOnBottom_True_TestThinTabView_WithLongNames ()
{
@@ -875,7 +878,7 @@ public class TabViewTests (ITestOutputHelper output)
);
}
- [Fact (Skip = "#3789 Broke. The right way to fix is to refactor TabView to separate Layout and Draw")]
+ [Fact]
[SetupFakeDriver]
public void ShowTopLine_True_TabsOnBottom_False_TestTabView_Width3 ()
{
@@ -919,7 +922,7 @@ public class TabViewTests (ITestOutputHelper output)
);
}
- [Fact (Skip = "#3789 Broke. The right way to fix is to refactor TabView to separate Layout and Draw")]
+ [Fact]
[SetupFakeDriver]
public void ShowTopLine_True_TabsOnBottom_False_TestThinTabView_WithLongNames ()
{
@@ -1009,7 +1012,7 @@ public class TabViewTests (ITestOutputHelper output)
);
}
- [Fact (Skip = "#3789 Broke. The right way to fix is to refactor TabView to separate Layout and Draw")]
+ [Fact]
[SetupFakeDriver]
public void ShowTopLine_True_TabsOnBottom_False_With_Unicode ()
{
@@ -1050,7 +1053,7 @@ public class TabViewTests (ITestOutputHelper output)
);
}
- [Fact (Skip = "#3789 Broke. The right way to fix is to refactor TabView to separate Layout and Draw")]
+ [Fact]
[SetupFakeDriver]
public void ShowTopLine_True_TabsOnBottom_True_TestTabView_Width3 ()
{
@@ -1098,7 +1101,7 @@ public class TabViewTests (ITestOutputHelper output)
);
}
- [Fact (Skip = "#3789 Broke. The right way to fix is to refactor TabView to separate Layout and Draw")]
+ [Fact]
[SetupFakeDriver]
public void ShowTopLine_True_TabsOnBottom_True_TestThinTabView_WithLongNames ()
{
@@ -1174,7 +1177,7 @@ public class TabViewTests (ITestOutputHelper output)
);
}
- [Fact (Skip = "#3789 Broke. The right way to fix is to refactor TabView to separate Layout and Draw")]
+ [Fact]
[SetupFakeDriver]
public void ShowTopLine_True_TabsOnBottom_True_With_Unicode ()
{