diff --git a/Terminal.Gui/Views/Tab.cs b/Terminal.Gui/Views/Tab.cs index b683b04b6..52fb0bdf9 100644 --- a/Terminal.Gui/Views/Tab.cs +++ b/Terminal.Gui/Views/Tab.cs @@ -22,7 +22,7 @@ public class Tab : View set { _displayText = value; - SetNeedsDraw (); + SetNeedsLayout (); } } diff --git a/Terminal.Gui/Views/TabView.cs b/Terminal.Gui/Views/TabView.cs index 99d90bec2..1a4d7c6a5 100644 --- a/Terminal.Gui/Views/TabView.cs +++ b/Terminal.Gui/Views/TabView.cs @@ -11,7 +11,7 @@ public class TabView : View /// This sub view is the main client area of the current tab. It hosts the of the tab, the /// . /// - private readonly View _contentView; + private readonly View _containerView; private readonly List _tabs = new (); @@ -28,14 +28,11 @@ public class TabView : View CanFocus = true; TabStop = TabBehavior.TabStop; // Because TabView has focusable subviews, it must be a TabGroup _tabsBar = new TabRowView (this); - _contentView = new View () - { - //Id = "TabView._contentView", - }; + _containerView = new (); ApplyStyleChanges (); base.Add (_tabsBar); - base.Add (_contentView); + base.Add (_containerView); // Things this view knows how to do AddCommand (Command.Left, () => SwitchTabBy (-1)); @@ -101,6 +98,9 @@ public class TabView : View /// public uint MaxTabTextWidth { get; set; } = DefaultMaxTabTextWidth; + // This is needed to hold initial value because it may change during the setter process + private bool _selectedTabHasFocus; + /// The currently selected member of chosen by the user. /// public Tab? SelectedTab @@ -108,17 +108,16 @@ public class TabView : View get => _selectedTab; set { - UnSetCurrentTabs (); - Tab? old = _selectedTab; + _selectedTabHasFocus = old is { } && (old.HasFocus || !_containerView.CanFocus); if (_selectedTab is { }) { if (_selectedTab.View is { }) { - _selectedTab.View.CanFocusChanged -= ContentViewCanFocus!; + _selectedTab.View.CanFocusChanged -= ContainerViewCanFocus!; // remove old content - _contentView.Remove (_selectedTab.View); + _containerView.Remove (_selectedTab.View); } } @@ -127,18 +126,17 @@ public class TabView : View // add new content if (_selectedTab?.View != null) { - _selectedTab.View.CanFocusChanged += ContentViewCanFocus!; - _contentView.Add (_selectedTab.View); - // _contentView.Id = $"_contentView for {_selectedTab.DisplayText}"; + _selectedTab.View.CanFocusChanged += ContainerViewCanFocus!; + _containerView.Add (_selectedTab.View); } - ContentViewCanFocus (null!, null!); + ContainerViewCanFocus (null!, null!); EnsureSelectedTabIsVisible (); if (old != _selectedTab) { - if (old?.HasFocus == true) + if (_selectedTabHasFocus || !_containerView.CanFocus) { SelectedTab?.SetFocus (); } @@ -149,9 +147,9 @@ public class TabView : View } } - private void ContentViewCanFocus (object sender, EventArgs eventArgs) + private void ContainerViewCanFocus (object sender, EventArgs eventArgs) { - _contentView.CanFocus = _contentView.Subviews.Count (v => v.CanFocus) > 0; + _containerView.CanFocus = _containerView.Subviews.Count (v => v.CanFocus) > 0; } private TabStyle _style = new (); @@ -220,34 +218,34 @@ public class TabView : View /// public void ApplyStyleChanges () { - _contentView.BorderStyle = Style.ShowBorder ? LineStyle.Single : LineStyle.None; - _contentView.Width = Dim.Fill (); + _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) { - _contentView.Border.Thickness = new Thickness (1, 1, 1, 0); + _containerView.Border!.Thickness = new Thickness (1, 1, 1, 0); } - _contentView.Y = 0; + _containerView.Y = 0; int tabHeight = GetTabHeight (false); // Fill client area leaving space at bottom for tabs - _contentView.Height = Dim.Fill (tabHeight); + _containerView.Height = Dim.Fill (tabHeight); _tabsBar.Height = tabHeight; - _tabsBar.Y = Pos.Bottom (_contentView); + _tabsBar.Y = Pos.Bottom (_containerView); } else { // Tabs are along the top if (Style.ShowBorder) { - _contentView.Border.Thickness = new Thickness (1, 0, 1, 1); + _containerView.Border!.Thickness = new Thickness (1, 0, 1, 1); } _tabsBar.Y = 0; @@ -255,10 +253,10 @@ public class TabView : View int tabHeight = GetTabHeight (true); //move content down to make space for tabs - _contentView.Y = Pos.Bottom (_tabsBar); + _containerView.Y = Pos.Bottom (_tabsBar); // Fill client area leaving space at bottom for border - _contentView.Height = Dim.Fill (); + _containerView.Height = Dim.Fill (); // The top tab should be 2 or 3 rows high and on the top @@ -270,6 +268,14 @@ public class TabView : View SetNeedsLayout (); } + /// + protected override void OnViewportChanged (DrawEventArgs e) + { + _tabLocations = CalculateViewport (Viewport).ToArray (); + + base.OnViewportChanged (e); + } + /// Updates to ensure that is visible. public void EnsureSelectedTabIsVisible () { @@ -295,7 +301,7 @@ public class TabView : View /// protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedView) { - if (SelectedTab is { } && !_contentView.CanFocus && focusedView == this) + if (SelectedTab is { HasFocus: false } && !_containerView.CanFocus && focusedView == this) { SelectedTab?.SetFocus (); @@ -305,25 +311,6 @@ public class TabView : View base.OnHasFocusChanged (newHasFocus, previousFocusedView, focusedView); } - /// - protected override bool OnDrawingContent () - { - if (Tabs.Any ()) - { - // Region savedClip = SetClip (); - _tabsBar.Draw (); - _contentView.SetNeedsDraw (); - _contentView.Draw (); - - //if (Driver is { }) - //{ - // Driver.Clip = savedClip; - //} - } - - return true; - } - /// /// Removes the given from . Caller is responsible for disposing the /// tab's hosted if appropriate. @@ -451,7 +438,7 @@ public class TabView : View { if (prevTab is { }) { - tab.X = Pos.Right (prevTab); + tab.X = Pos.Right (prevTab) - 1; } else { @@ -463,15 +450,11 @@ public class TabView : View // while there is space for the tab int tabTextWidth = tab.DisplayText.EnumerateRunes ().Sum (c => c.GetColumns ()); - string text = tab.DisplayText; - // The maximum number of characters to use for the tab name as specified // by the user (MaxTabTextWidth). But not more than the width of the view // or we won't even be able to render a single tab! long maxWidth = Math.Max (0, Math.Min (bounds.Width - 3, MaxTabTextWidth)); - prevTab = tab; - tab.Width = 2; tab.Height = Style.ShowTopLine ? 3 : 2; @@ -480,17 +463,22 @@ public class TabView : View { tab.Visible = true; tab.MouseClick += Tab_MouseClick!; + tab.Border!.MouseClick += Tab_MouseClick!; - yield return new TabToRender (tab, string.Empty, Equals (SelectedTab, tab)); + yield return new (tab, Equals (SelectedTab, tab)); break; } if (tabTextWidth > maxWidth) { - text = tab.DisplayText.Substring (0, (int)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; @@ -506,11 +494,19 @@ public class TabView : View // there is enough space! tab.Visible = true; tab.MouseClick += Tab_MouseClick!; + tab.Border!.MouseClick += Tab_MouseClick!; - yield return new TabToRender (tab, text, Equals (SelectedTab, tab)); + yield return new (tab, Equals (SelectedTab, tab)); + + prevTab = tab; i += tabTextWidth + 1; } + + if (_selectedTabHasFocus) + { + SelectedTab?.SetFocus (); + } } /// @@ -542,11 +538,27 @@ public class TabView : View private void UnSetCurrentTabs () { - if (_tabLocations is { }) + 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 (TabToRender tabToRender in _tabLocations) { tabToRender.Tab.MouseClick -= Tab_MouseClick!; + tabToRender.Tab.Border!.MouseClick -= Tab_MouseClick!; tabToRender.Tab.Visible = false; } @@ -570,7 +582,6 @@ public class TabView : View Id = "tabRowView"; CanFocus = true; - Height = 1; // BUGBUG: Views should avoid setting Height as doing so implies Frame.Size == GetContentSize (). Width = Dim.Fill (); _rightScrollIndicator = new View @@ -598,17 +609,23 @@ public class TabView : View protected override bool OnMouseEvent (MouseEventArgs me) { - Tab? hit = me.View as Tab; + 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)); + _host.OnTabClicked (new TabMouseEventArgs (hit!, me)); // user canceled click if (me.Handled) { return true; } + + if (parent == _host.SelectedTab) + { + _host.SelectedTab?.SetFocus (); + } } if (!me.IsSingleDoubleOrTripleClicked) @@ -625,11 +642,11 @@ public class TabView : View { var scrollIndicatorHit = 0; - if (me.View is { } && me.View.Id == "rightScrollIndicator") + if (me.View is { Id: "rightScrollIndicator" }) { scrollIndicatorHit = 1; } - else if (me.View is { } && me.View.Id == "leftScrollIndicator") + else if (me.View is { Id: "leftScrollIndicator" }) { scrollIndicatorHit = -1; } @@ -656,15 +673,20 @@ public class TabView : View } /// - protected override bool OnClearingViewport () + protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedView) { - // clear any old text - ClearViewport (); + if (_host.SelectedTab is { HasFocus: false, CanFocus: true } && focusedView == this) + { + _host.SelectedTab?.SetFocus (); - return true; + return; + } + + base.OnHasFocusChanged (newHasFocus, previousFocusedView, focusedView); } - protected override bool OnDrawingContent () + /// + protected override void OnSubviewLayout (LayoutEventArgs args) { _host._tabLocations = _host.CalculateViewport (Viewport).ToArray (); @@ -672,20 +694,18 @@ public class TabView : View RenderUnderline (); - SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ()); - - return true; + base.OnSubviewLayout (args); } /// - protected override bool OnDrawingSubviews () + protected override bool OnRenderingLineCanvas () { - // RenderTabLine (); + RenderTabLineCanvas (); return false; } - protected override void OnDrawComplete () + private void RenderTabLineCanvas () { if (_host._tabLocations is null) { @@ -694,12 +714,12 @@ public class TabView : View TabToRender [] tabLocations = _host._tabLocations; int selectedTab = -1; + var lc = new LineCanvas (); for (var i = 0; i < tabLocations.Length; i++) { View tab = tabLocations [i].Tab; Rectangle vts = tab.ViewportToScreen (tab.Viewport); - var lc = new LineCanvas (); int selectedOffset = _host.Style.ShowTopLine && tabLocations [i].IsSelected ? 0 : 1; if (tabLocations [i].IsSelected) @@ -1048,7 +1068,7 @@ public class TabView : View } } - if (i == 0 && i != selectedTab && _host.TabScrollOffset == 0 && _host.Style.ShowBorder) + if (i == 0 && i != selectedTab && _host is { TabScrollOffset: 0, Style.ShowBorder: true }) { if (_host.Style.TabsOnBottom) { @@ -1163,6 +1183,7 @@ public class TabView : View } else { + // Right corner if (_host.Style.TabsOnBottom) { lc.AddLine ( @@ -1213,12 +1234,9 @@ public class TabView : View } } } - - tab.LineCanvas.Merge (lc); - tab.RenderLineCanvas (); - - // RenderUnderline (); } + + _host.LineCanvas.Merge (lc); } private int GetUnderlineYPosition () @@ -1234,9 +1252,7 @@ public class TabView : View /// Renders the line with the tab names in it. private void RenderTabLine () { - TabToRender []? tabLocations = _host._tabLocations; - - if (tabLocations is null) + if (_host._tabLocations is null) { return; } @@ -1244,7 +1260,7 @@ public class TabView : View View? selected = null; int topLine = _host.Style.ShowTopLine ? 1 : 0; - foreach (TabToRender toRender in tabLocations) + foreach (TabToRender toRender in _host._tabLocations) { Tab tab = toRender.Tab; @@ -1254,80 +1270,45 @@ public class TabView : View if (_host.Style.TabsOnBottom) { - tab.Border.Thickness = new Thickness (1, 0, 1, topLine); - tab.Margin.Thickness = new Thickness (0, 1, 0, 0); + tab.Border!.Thickness = new (1, 0, 1, topLine); + tab.Margin!.Thickness = new (0, 1, 0, 0); } else { - tab.Border.Thickness = new Thickness (1, topLine, 1, 0); - tab.Margin.Thickness = new Thickness (0, 0, 0, topLine); + 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 Thickness (1, 1, 0, topLine); - tab.Margin.Thickness = new Thickness (0, 0, 0, 0); + tab.Border!.Thickness = new (1, 1, 1, topLine); + tab.Margin!.Thickness = new (0, 0, 0, 0); } else { - tab.Border.Thickness = new Thickness (1, topLine, 0, 1); - tab.Margin.Thickness = new Thickness (0, 0, 0, 0); + tab.Border!.Thickness = new (1, topLine, 1, 1); + tab.Margin!.Thickness = new (0, 0, 0, 0); } - - tab.Width = Math.Max (tab.Width!.GetAnchor (0) - 1, 1); } else { if (_host.Style.TabsOnBottom) { - tab.Border.Thickness = new Thickness (0, 1, 1, topLine); - tab.Margin.Thickness = new Thickness (0, 0, 0, 0); + tab.Border!.Thickness = new (1, 1, 1, topLine); + tab.Margin!.Thickness = new (0, 0, 0, 0); } else { - tab.Border.Thickness = new Thickness (0, topLine, 1, 1); - tab.Margin.Thickness = new Thickness (0, 0, 0, 0); - } - - tab.Width = Math.Max (tab.Width!.GetAnchor (0) - 1, 1); - } - - tab.Text = toRender.TextToRender; - - // BUGBUG: Layout should only be called from Mainloop iteration! - Layout (); - - tab.DrawBorderAndPadding (); - - Attribute prevAttr = Driver?.GetAttribute () ?? Attribute.Default; - - // if tab is the selected one and focus is inside this control - if (toRender.IsSelected && _host.HasFocus) - { - if (_host.Focused == this) - { - // if focus is the tab bar itself then show that they can switch tabs - prevAttr = ColorScheme.HotFocus; - } - else - { - // Focus is inside the tab - prevAttr = ColorScheme.HotNormal; + tab.Border!.Thickness = new (1, topLine, 1, 1); + tab.Margin!.Thickness = new (0, 0, 0, 0); } } - tab.TextFormatter.Draw ( - tab.ViewportToScreen (tab.Viewport), - prevAttr, - ColorScheme.HotNormal - ); - - tab.DrawBorderAndPadding (); - - - SetAttribute (GetNormalColor ()); + // Ensures updating TextFormatter constrains + tab.TextFormatter.ConstrainToWidth = tab.GetContentSize ().Width; + tab.TextFormatter.ConstrainToHeight = tab.GetContentSize ().Height; } } @@ -1356,7 +1337,6 @@ public class TabView : View // Ensures this is clicked instead of the first tab MoveSubviewToEnd (_leftScrollIndicator); - _leftScrollIndicator.Draw (); } else { @@ -1374,7 +1354,6 @@ public class TabView : View // Ensures this is clicked instead of the last tab if under this MoveSubviewToStart (_rightScrollIndicator); - _rightScrollIndicator.Draw (); } else { @@ -1387,11 +1366,10 @@ public class TabView : View private class TabToRender { - public TabToRender (Tab tab, string textToRender, bool isSelected) + public TabToRender (Tab tab, bool isSelected) { Tab = tab; IsSelected = isSelected; - TextToRender = textToRender; } /// True if the tab that is being rendered is the selected one. @@ -1399,6 +1377,5 @@ public class TabView : View public bool IsSelected { get; } public Tab Tab { get; } - public string TextToRender { get; } } } diff --git a/UnitTests/View/Adornment/PaddingTests.cs b/UnitTests/View/Adornment/PaddingTests.cs index 0242bcc1d..a4defc997 100644 --- a/UnitTests/View/Adornment/PaddingTests.cs +++ b/UnitTests/View/Adornment/PaddingTests.cs @@ -33,5 +33,7 @@ PPP", output ); TestHelpers.AssertDriverAttributesAre ("0", output, null, view.GetNormalColor ()); + + ((FakeDriver)Application.Driver!).End (); } } diff --git a/UnitTests/View/Layout/SetLayoutTests.cs b/UnitTests/View/Layout/SetLayoutTests.cs index 4859957d6..ce0addd54 100644 --- a/UnitTests/View/Layout/SetLayoutTests.cs +++ b/UnitTests/View/Layout/SetLayoutTests.cs @@ -813,5 +813,105 @@ public class SetLayoutTests (ITestOutputHelper output) t.Dispose (); } + [Fact] + [SetupFakeDriver] + public void Pos_Right_With_Adornments () + { + View view1 = new () { Text = "View1", Width = 7, Height = 3, BorderStyle = LineStyle.Rounded }; + View view2 = new () { Text = "View2", X = Pos.Right (view1) - 1, Width = 7, Height = 3, BorderStyle = LineStyle.Rounded }; + View view3 = new () { Text = "View3", X = Pos.Right (view2) - 1, Width = 7, Height = 3, BorderStyle = LineStyle.Rounded }; + View container = new () { Width = Dim.Fill (), Height = 3 }; + container.Add (view1, view2, view3); + View view4 = new () { Text = "View4", Y = Pos.Bottom (container), Width = 21, Height = 3, BorderStyle = LineStyle.Rounded }; + View superView = new () { Width = Dim.Fill (), Height = Dim.Fill () }; + superView.Add (container, view4); + + superView.Layout (); + superView.Draw (); + + TestHelpers.AssertDriverContentsAre ( + @" +╭─────╭─────╭─────╮ +│View1│View2│View3│ +╰─────╰─────╰─────╯ +╭───────────────────╮ +│View4 │ +╰───────────────────╯ +", + output + ); + + // Remove border bottom from the view1 + view1.Border!.Thickness = new (1, 1, 1, 0); + // Insert margin bottom into the view1 + view1.Margin!.Thickness = new (0, 0, 0, 1); + + View.SetClipToScreen (); + superView.Draw (); + + TestHelpers.AssertDriverContentsAre ( + @" +╭─────╭─────╭─────╮ +│View1│View2│View3│ + ╰─────╰─────╯ +╭───────────────────╮ +│View4 │ +╰───────────────────╯ +", + output + ); + + // Restore view1 border + view1.Border.Thickness = new (1); + // Restore view1 margin + view1.Margin.Thickness = Thickness.Empty; + + // Remove border bottom from the view2 + view2.Border!.Thickness = new (1, 1, 1, 0); + // Insert margin bottom into the view2 + view2.Margin!.Thickness = new (0, 0, 0, 1); + + View.SetClipToScreen (); + superView.Draw (); + + TestHelpers.AssertDriverContentsAre ( + @" +╭─────╭─────╭─────╮ +│View1│View2│View3│ +╰─────╯ ╰─────╯ +╭───────────────────╮ +│View4 │ +╰───────────────────╯ +", + output + ); + + // Restore view2 border + view2.Border.Thickness = new (1); + // Restore view2 margin + view2.Margin.Thickness = Thickness.Empty; + + // Remove border bottom from the view3 + view3.Border!.Thickness = new (1, 1, 1, 0); + // Insert margin bottom into the view3 + view3.Margin!.Thickness = new (0, 0, 0, 1); + + View.SetClipToScreen (); + superView.Draw (); + + TestHelpers.AssertDriverContentsAre ( + @" +╭─────╭─────╭─────╮ +│View1│View2│View3│ +╰─────╰─────╯ +╭───────────────────╮ +│View4 │ +╰───────────────────╯ +", + output + ); + + superView.Dispose (); + } } diff --git a/UnitTests/Views/TabViewTests.cs b/UnitTests/Views/TabViewTests.cs index 3999142fa..1ab880b7f 100644 --- a/UnitTests/Views/TabViewTests.cs +++ b/UnitTests/Views/TabViewTests.cs @@ -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