From c5a2bac74f7059c792399c1fbe33d56be8f00ff5 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 5 Nov 2024 17:40:51 -0700 Subject: [PATCH 1/3] Rebuildling TabView - WIP --- Terminal.Gui/Views/Tab.cs | 8 +- Terminal.Gui/Views/TabChangedEventArgs.cs | 20 +- Terminal.Gui/Views/TabView.cs | 1453 ++++++--------------- UICatalog/Scenarios/Editor.cs | 6 +- UICatalog/Scenarios/Images.cs | 10 +- UICatalog/Scenarios/Notepad.cs | 13 +- UICatalog/Scenarios/TabViewExample.cs | 4 +- 7 files changed, 446 insertions(+), 1068 deletions(-) diff --git a/Terminal.Gui/Views/Tab.cs b/Terminal.Gui/Views/Tab.cs index b683b04b6f..23bad4977c 100644 --- a/Terminal.Gui/Views/Tab.cs +++ b/Terminal.Gui/Views/Tab.cs @@ -1,4 +1,6 @@ #nullable enable +using System.Net.Security; + namespace Terminal.Gui; /// A single tab in a . @@ -11,7 +13,9 @@ public Tab () { BorderStyle = LineStyle.Rounded; CanFocus = true; - TabStop = TabBehavior.NoStop; + TabStop = TabBehavior.TabStop; + Width = Dim.Auto (DimAutoStyle.Text); + SuperViewRendersLineCanvas = true; } /// The text to display in a . @@ -26,7 +30,7 @@ public string DisplayText } } - /// The control to display when the tab is selected. + /// The View that will be made visible in the content area when the tab is selected. /// public View? View { get; set; } } diff --git a/Terminal.Gui/Views/TabChangedEventArgs.cs b/Terminal.Gui/Views/TabChangedEventArgs.cs index 0fe003f617..6c959867aa 100644 --- a/Terminal.Gui/Views/TabChangedEventArgs.cs +++ b/Terminal.Gui/Views/TabChangedEventArgs.cs @@ -1,20 +1,20 @@ namespace Terminal.Gui; -/// Describes a change in +/// Describes a change in public class TabChangedEventArgs : EventArgs { /// Documents a tab change - /// - /// - public TabChangedEventArgs (Tab oldTab, Tab newTab) + /// + /// + public TabChangedEventArgs (int? oldTabIndex, int? newTabIndex) { - OldTab = oldTab; - NewTab = newTab; + OldTabIndex = oldTabIndex; + NewTabIndex = newTabIndex; } - /// The currently selected tab. May be null - public Tab NewTab { get; } + /// The currently selected tab. + public int? NewTabIndex { get; } - /// The previously selected tab. May be null - public Tab OldTab { get; } + /// The previously selected tab. + public int? OldTabIndex{ get; } } diff --git a/Terminal.Gui/Views/TabView.cs b/Terminal.Gui/Views/TabView.cs index 99d90bec28..4e4eca828f 100644 --- a/Terminal.Gui/Views/TabView.cs +++ b/Terminal.Gui/Views/TabView.cs @@ -1,41 +1,35 @@ #nullable enable +using System.Linq; +using static Terminal.Gui.SpinnerStyle; +using static Unix.Terminal.Delegates; + namespace Terminal.Gui; /// Control that hosts multiple sub views, presenting a single one at once. -public class TabView : View +public class TabView : View, IDesignable { /// The default to set on new controls. public const uint DefaultMaxTabTextWidth = 30; - /// - /// 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 List _tabs = new (); - - /// This sub view is the 2 or 3 line control that represents the actual tabs themselves. - private readonly TabRowView _tabsBar; + /// This SubView is the 2 or 3 line control that represents the actual tabs themselves. + private readonly TabRowView _tabRowView; - private Tab? _selectedTab; - private TabToRender []? _tabLocations; - private int _tabScrollOffset; + // private TabToRender []? _tabLocations; /// Initializes a class. public TabView () { 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", - }; - ApplyStyleChanges (); - base.Add (_tabsBar); - base.Add (_contentView); + Width = Dim.Fill (); + Height = Dim.Auto (minimumContentDim: GetTabHeight (!Style.TabsOnBottom)); + + _tabRowView = new TabRowView (); + _tabRowView.Selecting += _tabRowView_Selecting; + base.Add (_tabRowView); + + ApplyStyleChanges (); // Things this view knows how to do AddCommand (Command.Left, () => SwitchTabBy (-1)); @@ -46,8 +40,8 @@ public TabView () Command.LeftStart, () => { - TabScrollOffset = 0; - SelectedTab = Tabs.FirstOrDefault ()!; + FirstVisibleTabIndex = 0; + SelectedTabIndex = 0; return true; } @@ -57,8 +51,8 @@ public TabView () Command.RightEnd, () => { - TabScrollOffset = Tabs.Count - 1; - SelectedTab = Tabs.LastOrDefault ()!; + FirstVisibleTabIndex = Tabs.Count - 1; + SelectedTabIndex = Tabs.Count - 1; return true; } @@ -68,8 +62,8 @@ public TabView () Command.PageDown, () => { - TabScrollOffset += _tabLocations!.Length; - SelectedTab = Tabs.ElementAt (TabScrollOffset); + // FirstVisibleTabIndex += _tabLocations!.Length; + SelectedTabIndex = FirstVisibleTabIndex; return true; } @@ -79,13 +73,67 @@ public TabView () Command.PageUp, () => { - TabScrollOffset -= _tabLocations!.Length; - SelectedTab = Tabs.ElementAt (TabScrollOffset); + // FirstVisibleTabIndex -= _tabLocations!.Length; + SelectedTabIndex = FirstVisibleTabIndex; return true; } ); + AddCommand (Command.ScrollLeft, () => + { + var visibleTabs = GetTabsThatCanBeVisible (Viewport).ToArray (); + int? first = visibleTabs.FirstOrDefault (); + + if (first > 0) + { + int scroll = -_tabRowView.Tabs.ToArray () [first.Value].Frame.Width; + _tabRowView.Viewport = _tabRowView.Viewport with { X = _tabRowView.Viewport.X + scroll }; + SetNeedsLayout (); + FirstVisibleTabIndex--; + return true; + } + + return false; + }); + + AddCommand (Command.ScrollRight, () => + { + var visibleTabs = GetTabsThatCanBeVisible (Viewport).ToArray (); + int? last = visibleTabs.LastOrDefault (); + + if (last is { }) + { + _tabRowView.ScrollHorizontal (_tabRowView.Tabs.ToArray () [last.Value + 1].Frame.Width); + SetNeedsLayout (); + FirstVisibleTabIndex++; + return true; + } + + return false; + }); + + //// Space or single-click - Raise Selecting + //AddCommand (Command.Select, (ctx) => + // { + // //if (RaiseSelecting (ctx) is true) + // //{ + // // return true; + // //} + + // if (ctx.Data is Tab tab) + // { + // int? current = SelectedTabIndex; + // SelectedTabIndex = _tabRowView.Tabs.ToArray ().IndexOf (tab); + // SetNeedsDraw (); + + // // e.Cancel = HasFocus; + // return true; + // } + + // return false; + // }); + // Default keybindings for this view KeyBindings.Add (Key.CursorLeft, Command.Left); KeyBindings.Add (Key.CursorRight, Command.Right); @@ -95,65 +143,108 @@ public TabView () KeyBindings.Add (Key.PageUp, Command.PageUp); } + private void _tabRowView_Selecting (object? sender, CommandEventArgs e) + { + if (e.Context.Data is int tabIndex) + { + int? current = SelectedTabIndex; + SelectedTabIndex = tabIndex; + Layout (); + e.Cancel = true; + } + } + + /// + protected override void OnSubviewLayout (LayoutEventArgs args) + { + _tabRowView.CalcContentSize (); + } + + /// + protected override void OnSubviewsLaidOut (LayoutEventArgs args) + { + // hide all that can't fit + var visibleTabs = GetTabsThatCanBeVisible (Viewport).ToArray (); + + for (var index = 0; index < _tabRowView.Tabs.ToArray ().Length; index++) + { + Tab tab = _tabRowView.Tabs.ToArray () [index]; + tab.Visible = visibleTabs.Contains (index); + } + } + + /// + public bool EnableForDesign () + { + AddTab (new () { Text = "Tab_1", Id = "tab1", View = new Label { Text = "Label in Tab1" } }, false); + AddTab (new () { Text = "Tab _2", Id = "tab2", View = new TextField { Text = "TextField in Tab2", Width = 10 } }, false); + AddTab (new () { Text = "Tab _Three", Id = "tab3", View = new Label { Text = "Label in Tab3" } }, false); + AddTab (new () { Text = "Tab _Quattro", Id = "tab4", View = new TextField { Text = "TextField in Tab4", Width = 10 } }, false); + + return true; + } + /// /// The maximum number of characters to render in a Tab header. This prevents one long tab from pushing out all /// the others. /// public uint MaxTabTextWidth { get; set; } = DefaultMaxTabTextWidth; + private int? _selectedTabIndex; + /// The currently selected member of chosen by the user. /// - public Tab? SelectedTab + public int? SelectedTabIndex { - get => _selectedTab; + get => _selectedTabIndex; set { - UnSetCurrentTabs (); - - Tab? old = _selectedTab; + // If value is outside the range of Tabs, throw an exception + if (value < 0 || value >= Tabs.Count) + { + throw new ArgumentOutOfRangeException (nameof (value), value, @"SelectedTab the range of Tabs."); + } - if (_selectedTab is { }) + if (value == _selectedTabIndex) { - if (_selectedTab.View is { }) - { - _selectedTab.View.CanFocusChanged -= ContentViewCanFocus!; - // remove old content - _contentView.Remove (_selectedTab.View); - } + return; } - _selectedTab = value; + int? old = _selectedTabIndex; + + // Get once to avoid multiple enumerations + Tab [] tabs = _tabRowView.Tabs.ToArray (); - // add new content - if (_selectedTab?.View != null) + if (_selectedTabIndex is { } && tabs [_selectedTabIndex.Value].View is { }) { - _selectedTab.View.CanFocusChanged += ContentViewCanFocus!; - _contentView.Add (_selectedTab.View); - // _contentView.Id = $"_contentView for {_selectedTab.DisplayText}"; + Remove (tabs [_selectedTabIndex.Value].View); } - ContentViewCanFocus (null!, null!); + _selectedTabIndex = value; + + if (_selectedTabIndex is { } && tabs [_selectedTabIndex.Value].View is { }) + { + Add (tabs [_selectedTabIndex.Value].View); + } EnsureSelectedTabIsVisible (); - if (old != _selectedTab) + if (_selectedTabIndex is { }) { - if (old?.HasFocus == true) + ApplyStyleChanges (); + + if (HasFocus) { - SelectedTab?.SetFocus (); + tabs [_selectedTabIndex.Value].View.SetFocus (); } - - OnSelectedTabChanged (old!, _selectedTab!); } + + OnSelectedTabIndexChanged (old, _selectedTabIndex!); + SelectedTabChanged?.Invoke (this, new TabChangedEventArgs (old, _selectedTabIndex)); 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 . @@ -167,6 +258,7 @@ public TabStyle Style { return; } + _style = value; SetNeedsLayout (); } @@ -174,342 +266,279 @@ public TabStyle Style /// All tabs currently hosted by the control. /// - public IReadOnlyCollection Tabs => _tabs.AsReadOnly (); - - /// When there are too many tabs to render, this indicates the first tab to render on the screen. - /// - public int TabScrollOffset + public IReadOnlyCollection Tabs => _tabRowView.Tabs.ToArray ().AsReadOnly (); + + private int _firstVisibleTabIndex; + + /// Gets or sets the index of first visible tab. This enables horizontal scrolling of the tabs. + /// + /// + /// On set, if the value is less than 0, it will be set to 0. If the value is greater than the number of tabs + /// it will be set to the last tab index. + /// + /// + public int FirstVisibleTabIndex { - get => _tabScrollOffset; + get => _firstVisibleTabIndex; set { - _tabScrollOffset = EnsureValidScrollOffsets (value); + _firstVisibleTabIndex = Math.Max (Math.Min (value, Tabs.Count - 1), 0); + ; SetNeedsLayout (); } } /// Adds the given to . /// - /// True to make the newly added Tab the . + /// True to make the newly added Tab the . public void AddTab (Tab tab, bool andSelect) { - if (_tabs.Contains (tab)) + // Ok to use Subviews here instead of Tabs + if (_tabRowView.Subviews.Contains (tab)) { return; } - _tabs.Add (tab); - _tabsBar.Add (tab); + // Add to the TabRowView as a subview + _tabRowView.Add (tab); - if (SelectedTab is null || andSelect) + if (_tabRowView.Tabs.Count () == 1 || andSelect) { - SelectedTab = tab; + SelectedTabIndex = _tabRowView.Tabs.Count () - 1; EnsureSelectedTabIsVisible (); - tab.View?.SetFocus (); + if (HasFocus) + { + tab.View?.SetFocus (); + } } + ApplyStyleChanges (); SetNeedsLayout (); } + /// - /// Updates the control to use the latest state settings in . This can change the size of the - /// client area of the tab (for rendering the selected tab's content). This method includes a call to - /// . + /// Removes the given from . Caller is responsible for disposing the + /// tab's hosted if appropriate. /// - public void ApplyStyleChanges () + /// + public void RemoveTab (Tab? tab) { - _contentView.BorderStyle = Style.ShowBorder ? LineStyle.Single : LineStyle.None; - _contentView.Width = Dim.Fill (); + if (tab is null || !_tabRowView.Subviews.Contains (tab)) + { + return; + } - if (Style.TabsOnBottom) + int idx = _tabRowView.Tabs.ToArray ().IndexOf (tab); + if (idx == SelectedTabIndex) { - // Tabs are along the bottom so just dodge the border - if (Style.ShowBorder) - { - _contentView.Border.Thickness = new Thickness (1, 1, 1, 0); - } + SelectedTabIndex = null; + } - _contentView.Y = 0; + _tabRowView.Remove (tab); - int tabHeight = GetTabHeight (false); + // Get once to avoid multiple enumerations + Tab [] tabs = _tabRowView.Tabs.ToArray (); - // Fill client area leaving space at bottom for tabs - _contentView.Height = Dim.Fill (tabHeight); + if (SelectedTabIndex is null) + { + // Either no tab was previously selected or the selected tab was removed - _tabsBar.Height = tabHeight; + // select the tab closest to the one that disappeared + int toSelect = Math.Max (idx - 1, 0); - _tabsBar.Y = Pos.Bottom (_contentView); - } - else - { - // Tabs are along the top - if (Style.ShowBorder) + if (toSelect < tabs.Length) { - _contentView.Border.Thickness = new Thickness (1, 0, 1, 1); + SelectedTabIndex = toSelect; } + else + { + SelectedTabIndex = tabs.Length - 1; + } + } - _tabsBar.Y = 0; - - int tabHeight = GetTabHeight (true); - - //move content down to make space for tabs - _contentView.Y = Pos.Bottom (_tabsBar); - - // Fill client area leaving space at bottom for border - _contentView.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 + if (SelectedTabIndex > tabs.Length - 1) + { + // Removing the tab, caused the selected tab to be out of range + SelectedTabIndex = tabs.Length - 1; } + EnsureSelectedTabIsVisible (); SetNeedsLayout (); } - /// Updates to ensure that is visible. - public void EnsureSelectedTabIsVisible () + /// + /// Applies the settings in . This can change the dimensions of + /// (for rendering the selected tab's content). This method includes a call to + /// . + /// + public void ApplyStyleChanges () { - if (!IsInitialized || SelectedTab is null) + // Get once to avoid multiple enumerations + Tab [] tabs = _tabRowView.Tabs.ToArray (); + + View? selectedView = null; + + if (SelectedTabIndex is { }) { - return; + selectedView = tabs [SelectedTabIndex.Value].View; } - // if current viewport does not include the selected tab - if (!CalculateViewport (Viewport).Any (r => Equals (SelectedTab, r.Tab))) + if (selectedView is { }) { - // Set scroll offset so the first tab rendered is the - TabScrollOffset = Math.Max (0, Tabs.IndexOf (SelectedTab)); + selectedView.BorderStyle = Style.ShowBorder ? LineStyle.Single : LineStyle.None; + selectedView.Width = Dim.Fill (); } - } - /// Updates to be a valid index of . - /// The value to validate. - /// Changes will not be immediately visible in the display until you call . - /// The valid for the given value. - public int EnsureValidScrollOffsets (int value) { return Math.Max (Math.Min (value, Tabs.Count - 1), 0); } + int tabHeight = GetTabHeight (!Style.TabsOnBottom); - /// - protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedView) - { - if (SelectedTab is { } && !_contentView.CanFocus && focusedView == this) + if (Style.TabsOnBottom) { - SelectedTab?.SetFocus (); + _tabRowView.Height = tabHeight; + _tabRowView.Y = Pos.AnchorEnd (); - return; + if (selectedView is { }) + { + // Tabs are along the bottom so just dodge the border + if (Style.ShowBorder && selectedView?.Border is { }) + { + selectedView.Border.Thickness = new Thickness (1, 1, 1, 0); + } + + // Fill client area leaving space at bottom for tabs + selectedView!.Y = 0; + selectedView.Height = Dim.Fill (tabHeight); + } } + else + { + // Tabs are along the top + _tabRowView.Height = tabHeight; + _tabRowView.Y = 0; - base.OnHasFocusChanged (newHasFocus, previousFocusedView, focusedView); - } + if (selectedView is { }) + { + if (Style.ShowBorder && selectedView.Border is { }) + { + selectedView.Border.Thickness = new Thickness (1, 0, 1, 1); + } - /// - protected override bool OnDrawingContent () - { - if (Tabs.Any ()) - { - // Region savedClip = SetClip (); - _tabsBar.Draw (); - _contentView.SetNeedsDraw (); - _contentView.Draw (); - //if (Driver is { }) - //{ - // Driver.Clip = savedClip; - //} + //move content down to make space for tabs + selectedView.Y = Pos.Bottom (_tabRowView); + + // Fill client area leaving space at bottom for border + selectedView.Height = Dim.Fill (); + } } - return true; + SetNeedsLayout (); } - /// - /// Removes the given from . Caller is responsible for disposing the - /// tab's hosted if appropriate. - /// - /// - public void RemoveTab (Tab? tab) + /// Updates to ensure that is visible. + public void EnsureSelectedTabIsVisible () { - if (tab is null || !_tabs.Contains (tab)) + if (SelectedTabIndex is null) { return; } - // what tab was selected before closing - int idx = _tabs.IndexOf (tab); - - _tabs.Remove (tab); + // Get once to avoid multiple enumerations + Tab [] tabs = _tabRowView.Tabs.ToArray (); + View? selectedView = tabs [SelectedTabIndex.Value].View; - // if the currently selected tab is no longer a member of Tabs - if (SelectedTab is null || !Tabs.Contains (SelectedTab)) + if (selectedView is null) { - // 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 (); - } + return; } - EnsureSelectedTabIsVisible (); - SetNeedsLayout (); + // if current viewport does not include the selected tab + if (!GetTabsThatCanBeVisible (Viewport).Any (r => Equals (SelectedTabIndex.Value, r))) + { + // Set scroll offset so the first tab rendered is the + FirstVisibleTabIndex = Math.Max (0, SelectedTabIndex.Value); + } } - /// Event for when changes. + /// Event for when changes. public event EventHandler? SelectedTabChanged; /// - /// Changes the by the given . Positive for right, negative for - /// left. If no tab is currently selected then the first tab will become selected. + /// Changes the by the given . Positive for right, negative for + /// left. If no tab is currently selected then the first tab will become selected. /// /// + /// if a change was made. public bool SwitchTabBy (int amount) { - if (Tabs.Count == 0) + + // Get once to avoid multiple enumerations + Tab [] tabs = _tabRowView.Tabs.ToArray (); + + if (tabs.Length == 0) { return false; } + int? currentIdx = SelectedTabIndex; + // if there is only one tab anyway or nothing is selected - if (Tabs.Count == 1 || SelectedTab is null) + if (tabs.Length == 1) { - SelectedTab = Tabs.ElementAt (0); + SelectedTabIndex = 0; - return SelectedTab is { }; + return SelectedTabIndex != currentIdx; } - int currentIdx = Tabs.IndexOf (SelectedTab); - // Currently selected tab has vanished! - if (currentIdx == -1) + if (currentIdx is null) { - SelectedTab = Tabs.ElementAt (0); + SelectedTabIndex = 0; + return true; } - int newIdx = Math.Max (0, Math.Min (currentIdx + amount, Tabs.Count - 1)); + int newIdx = Math.Max (0, Math.Min (currentIdx.Value + amount, tabs.Length - 1)); if (newIdx == currentIdx) { return false; } - SelectedTab = _tabs [newIdx]; - - EnsureSelectedTabIsVisible (); + SelectedTabIndex = newIdx; return true; } - /// - /// 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; - - /// Disposes the control and all . - /// - 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 (); - } - } - } - - /// Raises the event. - protected virtual void OnSelectedTabChanged (Tab oldTab, Tab newTab) - { - SelectedTabChanged?.Invoke (this, new TabChangedEventArgs (oldTab, newTab)); - } + /// Called when the has changed. + protected virtual void OnSelectedTabIndexChanged (int? oldTabIndex, int? newTabIndex) { } - /// Returns which tabs to render at each x location. + /// Returns which tabs will be visible given the dimensions of the TabView, which tab is selected, and how the tabs have been scrolled. + /// Same as this.Frame. /// - private IEnumerable CalculateViewport (Rectangle bounds) + private IEnumerable GetTabsThatCanBeVisible (Rectangle bounds) { - UnSetCurrentTabs (); - - var i = 1; + var curWidth = 1; View? prevTab = null; + // Get once to avoid multiple enumerations + Tab [] tabs = _tabRowView.Tabs.ToArray (); + // Starting at the first or scrolled to tab - foreach (Tab tab in Tabs.Skip (TabScrollOffset)) + for (int i = FirstVisibleTabIndex; i < tabs.Length; i++) { - if (prevTab is { }) + if (curWidth >= bounds.Width) { - tab.X = Pos.Right (prevTab); - } - else - { - tab.X = 0; - } - - tab.Y = 0; - - // 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; - - // if tab view is width <= 3 don't render any tabs - if (maxWidth == 0) - { - tab.Visible = true; - tab.MouseClick += Tab_MouseClick!; - - yield return new TabToRender (tab, string.Empty, Equals (SelectedTab, tab)); - break; } - if (tabTextWidth > maxWidth) - { - text = tab.DisplayText.Substring (0, (int)maxWidth); - tabTextWidth = (int)maxWidth; - } - - tab.Width = Math.Max (tabTextWidth + 2, 1); - tab.Height = Style.ShowTopLine ? 3 : 2; - - // if there is not enough space for this tab - if (i + tabTextWidth >= bounds.Width) + if (curWidth + tabs [i].Frame.Width < bounds.Width) { - tab.Visible = false; - - break; + yield return i; } - - // there is enough space! - tab.Visible = true; - tab.MouseClick += Tab_MouseClick!; - - yield return new TabToRender (tab, text, Equals (SelectedTab, tab)); - - i += tabTextWidth + 1; + curWidth += tabs [i].Frame.Width; } } @@ -535,870 +564,214 @@ private int GetTabHeight (bool top) return Style.ShowTopLine ? 3 : 2; } - private void Tab_MouseClick (object sender, MouseEventArgs e) - { - e.Handled = _tabsBar.NewMouseEvent (e) == true; - } - - private void UnSetCurrentTabs () + /// + protected override void Dispose (bool disposing) { - if (_tabLocations is { }) + if (disposing) { - foreach (TabToRender tabToRender in _tabLocations) + // Get once to avoid multiple enumerations + Tab [] tabs = _tabRowView.Tabs.ToArray (); + if (SelectedTabIndex is { }) { - tabToRender.Tab.MouseClick -= Tab_MouseClick!; - tabToRender.Tab.Visible = false; + Remove (tabs [SelectedTabIndex.Value].View); } - - _tabLocations = null; - } + foreach (Tab tab in tabs) + { + tab.View?.Dispose (); + tab.View = null; + } + }; + base.Dispose (disposing); } - /// Raises the event. - /// - private protected virtual void OnTabClicked (TabMouseEventArgs tabMouseEventArgs) { TabClicked?.Invoke (this, tabMouseEventArgs); } - private class TabRowView : View { - private readonly TabView _host; private readonly View _leftScrollIndicator; private readonly View _rightScrollIndicator; - public TabRowView (TabView host) + public TabRowView () { - _host = host; Id = "tabRowView"; CanFocus = true; - Height = 1; // BUGBUG: Views should avoid setting Height as doing so implies Frame.Size == GetContentSize (). + Height = Dim.Auto (); Width = Dim.Fill (); + SuperViewRendersLineCanvas = true; _rightScrollIndicator = new View { Id = "rightScrollIndicator", + X = Pos.Func (() => Viewport.X + Viewport.Width - 1), + Y = Pos.AnchorEnd (), Width = 1, Height = 1, - Visible = false, + Visible = true, Text = Glyphs.RightArrow.ToString () }; - _rightScrollIndicator.MouseClick += _host.Tab_MouseClick!; _leftScrollIndicator = new View { Id = "leftScrollIndicator", + X = Pos.Func (() => Viewport.X), + Y = Pos.AnchorEnd (), Width = 1, Height = 1, - Visible = false, + Visible = true, Text = Glyphs.LeftArrow.ToString () }; - _leftScrollIndicator.MouseClick += _host.Tab_MouseClick!; Add (_rightScrollIndicator, _leftScrollIndicator); + + Initialized += OnInitialized; } - protected override bool OnMouseEvent (MouseEventArgs me) + private void OnInitialized (object? sender, EventArgs e) { - Tab? hit = me.View as Tab; - - if (me.IsSingleClicked) - { - _host.OnTabClicked (new TabMouseEventArgs (hit, me)); - - // user canceled click - if (me.Handled) - { - return true; - } - } - - if (!me.IsSingleDoubleOrTripleClicked) - { - return false; - } - - if (!HasFocus && CanFocus) + if (SuperView is TabView tabView) { - SetFocus (); - } - - if (me.IsSingleDoubleOrTripleClicked) - { - var scrollIndicatorHit = 0; - - if (me.View is { } && me.View.Id == "rightScrollIndicator") - { - scrollIndicatorHit = 1; - } - else if (me.View is { } && me.View.Id == "leftScrollIndicator") - { - scrollIndicatorHit = -1; - } - - if (scrollIndicatorHit != 0) - { - _host.SwitchTabBy (scrollIndicatorHit); - - SetNeedsLayout (); - - return true; - } - - if (hit is { }) - { - _host.SelectedTab = hit; - SetNeedsLayout (); - - return true; - } + _leftScrollIndicator.MouseClick += (o, args) => + { + tabView.InvokeCommand (Command.ScrollLeft); + }; + _rightScrollIndicator.MouseClick += (o, args) => + { + tabView.InvokeCommand (Command.ScrollRight); + }; + tabView.SelectedTabChanged += TabView_SelectedTabChanged; } - return false; + CalcContentSize (); } - /// - protected override bool OnClearingViewport () + private void TabView_SelectedTabChanged (object? sender, TabChangedEventArgs e) { - // clear any old text - ClearViewport (); - - return true; + _selectedTabIndex = e.NewTabIndex; + CalcContentSize (); } - protected override bool OnDrawingContent () + /// + public override void OnAdded (SuperViewChangedEventArgs e) { - _host._tabLocations = _host.CalculateViewport (Viewport).ToArray (); - - RenderTabLine (); - - RenderUnderline (); - - SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ()); + if (e.SubView is Tab tab) + { + MoveSubviewToEnd (_leftScrollIndicator); + MoveSubviewToEnd (_rightScrollIndicator); - return true; + tab.HasFocusChanged += TabOnHasFocusChanged; + tab.Selecting += Tab_Selecting; + } + CalcContentSize (); } - /// - protected override bool OnDrawingSubviews () + private void Tab_Selecting (object? sender, CommandEventArgs e) { - // RenderTabLine (); - - return false; + e.Cancel = RaiseSelecting (new CommandContext (Command.Select, null, data: Tabs.ToArray ().IndexOf (sender))) is true; } - protected override void OnDrawComplete () + private void TabOnHasFocusChanged (object? sender, HasFocusEventArgs e) { - if (_host._tabLocations is null) - { - return; - } - - TabToRender [] tabLocations = _host._tabLocations; - int selectedTab = -1; + TabView? host = SuperView as TabView; - for (var i = 0; i < tabLocations.Length; i++) + if (host is null) { - 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) - { - 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.TabScrollOffset == 0 && _host.Style.ShowBorder) - { - if (_host.Style.TabsOnBottom) - { - // Upper left vertical line - lc.AddLine ( - new Point (vts.X - 1, vts.Y - 1), - 0, - Orientation.Vertical, - tab.BorderStyle - ); - - lc.AddLine ( - new Point (vts.X - 1, vts.Y - 1), - 1, - Orientation.Horizontal, - tab.BorderStyle - ); - } - else - { - // Lower left vertical line - lc.AddLine ( - new Point (vts.X - 1, vts.Bottom), - 0, - Orientation.Vertical, - tab.BorderStyle - ); - - lc.AddLine ( - new Point (vts.X - 1, vts.Bottom), - 1, - Orientation.Horizontal, - tab.BorderStyle - ); - } - } - - if (i == tabLocations.Length - 1 && i != selectedTab) - { - if (_host.Style.TabsOnBottom) - { - // Upper right tee - lc.AddLine ( - new Point (vts.Right, vts.Y - 1), - 1, - Orientation.Vertical, - tab.BorderStyle - ); - - lc.AddLine ( - new Point (vts.Right, vts.Y - 1), - 0, - Orientation.Horizontal, - tab.BorderStyle - ); - } - else - { - // Lower right tee - lc.AddLine ( - new Point (vts.Right, vts.Bottom), - -1, - Orientation.Vertical, - tab.BorderStyle - ); - - lc.AddLine ( - new Point (vts.Right, vts.Bottom), - 0, - Orientation.Horizontal, - tab.BorderStyle - ); - } - } - - if (i == tabLocations.Length - 1) - { - var arrowOffset = 1; - - 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 - { - if (_host.Style.TabsOnBottom) - { - lc.AddLine ( - new Point (vts.Right, vts.Y - lastSelectedTab), - lineLength, - Orientation.Horizontal, - tab.BorderStyle - ); - } - else - { - lc.AddLine ( - new Point (vts.Right, vts.Bottom - lastSelectedTab), - lineLength, - Orientation.Horizontal, - tab.BorderStyle - ); - } - - if (_host.Style.ShowBorder) - { - if (_host.Style.TabsOnBottom) - { - // More LRCorner - lc.AddLine ( - new Point ( - tabsBarVts.Right - 1, - vts.Y - lastSelectedTab - ), - -1, - Orientation.Vertical, - tab.BorderStyle - ); - } - else - { - // More URCorner - lc.AddLine ( - new Point ( - tabsBarVts.Right - 1, - vts.Bottom - lastSelectedTab - ), - 1, - Orientation.Vertical, - tab.BorderStyle - ); - } - } - } - } - - tab.LineCanvas.Merge (lc); - tab.RenderLineCanvas (); - - // RenderUnderline (); + return; } - } - private int GetUnderlineYPosition () - { - if (_host.Style.TabsOnBottom) - { - return 0; - } - return _host.Style.ShowTopLine ? 2 : 1; + //if (e is { NewFocused: Tab tab, NewValue: true }) + //{ + // e.Cancel = RaiseSInvokeCommand (Command.Select, new CommandContext () { Data = tab }) is true; + //} } - /// Renders the line with the tab names in it. - private void RenderTabLine () + public void CalcContentSize () { - TabToRender []? tabLocations = _host._tabLocations; + TabView? host = SuperView as TabView; - if (tabLocations is null) + if (host is null) { return; } - View? selected = null; - int topLine = _host.Style.ShowTopLine ? 1 : 0; + Tab? selected = null; + int topLine = host!.Style.ShowTopLine ? 1 : 0; + + Tab [] tabs = Tabs.ToArray (); - foreach (TabToRender toRender in tabLocations) + for (int i = 0; i < tabs.Length; i++) { - Tab tab = toRender.Tab; + tabs [i].Height = Dim.Fill (); + if (i == 0) + { + tabs [i].X = 0; + } + else + { + tabs [i].X = Pos.Right (tabs [i - 1]); + } - if (toRender.IsSelected) + if (i == _selectedTabIndex) { - selected = tab; + selected = tabs [i]; - if (_host.Style.TabsOnBottom) + if (host.Style.TabsOnBottom) { - tab.Border.Thickness = new Thickness (1, 0, 1, topLine); - tab.Margin.Thickness = new Thickness (0, 1, 0, 0); + tabs [i].Border.Thickness = new Thickness (1, 0, 1, topLine); + tabs [i].Margin.Thickness = new Thickness (0, 1, 0, 0); } else { - tab.Border.Thickness = new Thickness (1, topLine, 1, 0); - tab.Margin.Thickness = new Thickness (0, 0, 0, topLine); + tabs [i].Border.Thickness = new Thickness (1, topLine, 1, 0); + tabs [i].Margin.Thickness = new Thickness (0, 0, 0, topLine); } } else if (selected is null) { - if (_host.Style.TabsOnBottom) + if (host.Style.TabsOnBottom) { - tab.Border.Thickness = new Thickness (1, 1, 0, topLine); - tab.Margin.Thickness = new Thickness (0, 0, 0, 0); + tabs [i].Border.Thickness = new Thickness (1, 1, 0, topLine); + tabs [i].Margin.Thickness = new Thickness (0, 0, 0, 0); } else { - tab.Border.Thickness = new Thickness (1, topLine, 0, 1); - tab.Margin.Thickness = new Thickness (0, 0, 0, 0); + tabs [i].Border.Thickness = new Thickness (1, topLine, 0, 1); + tabs [i].Margin.Thickness = new Thickness (0, 0, 0, 0); } - tab.Width = Math.Max (tab.Width!.GetAnchor (0) - 1, 1); + //tabs [i].Width = Math.Max (tabs [i].Width!.GetAnchor (0) - 1, 1); } else { - if (_host.Style.TabsOnBottom) + if (host.Style.TabsOnBottom) { - tab.Border.Thickness = new Thickness (0, 1, 1, topLine); - tab.Margin.Thickness = new Thickness (0, 0, 0, 0); + tabs [i].Border.Thickness = new Thickness (0, 1, 1, topLine); + tabs [i].Margin.Thickness = new Thickness (0, 0, 0, 0); } else { - tab.Border.Thickness = new Thickness (0, topLine, 1, 1); - tab.Margin.Thickness = new Thickness (0, 0, 0, 0); + tabs [i].Border.Thickness = new Thickness (0, topLine, 1, 1); + tabs [i].Margin.Thickness = new Thickness (0, 0, 0, 0); } - tab.Width = Math.Max (tab.Width!.GetAnchor (0) - 1, 1); + //tabs [i].Width = Math.Max (tabs [i].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.TextFormatter.Draw ( - tab.ViewportToScreen (tab.Viewport), - prevAttr, - ColorScheme.HotNormal - ); - - tab.DrawBorderAndPadding (); - - - SetAttribute (GetNormalColor ()); - } - } - - /// Renders the line of the tab that adjoins the content of the tab. - private void RenderUnderline () - { - int y = GetUnderlineYPosition (); - - TabToRender? selected = _host._tabLocations?.FirstOrDefault (t => t.IsSelected); - - if (selected is null) - { - return; + //tabs [i].Text = toRender.TextToRender; } - // 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; + SetContentSize (null); + Layout (Application.Screen.Size); - // Ensures this is clicked instead of the first tab - MoveSubviewToEnd (_leftScrollIndicator); - _leftScrollIndicator.Draw (); - } - else + var width = 0; + foreach (Tab t in tabs) { - _leftScrollIndicator.Visible = false; + width += t.Frame.Width; } - - // 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); - _rightScrollIndicator.Draw (); - } - else - { - _rightScrollIndicator.Visible = false; - } - } - - private bool ShouldDrawRightScrollIndicator () { return _host._tabLocations!.LastOrDefault ()?.Tab != _host.Tabs.LastOrDefault (); } - } - - private class TabToRender - { - public TabToRender (Tab tab, string textToRender, bool isSelected) - { - Tab = tab; - IsSelected = isSelected; - TextToRender = textToRender; + SetContentSize (new (width, Viewport.Height)); } - /// True if the tab that is being rendered is the selected one. - /// - public bool IsSelected { get; } + internal IEnumerable Tabs => Subviews.Where (v => v is Tab).Cast (); - public Tab Tab { get; } - public string TextToRender { get; } + private int? _selectedTabIndex = null; } } diff --git a/UICatalog/Scenarios/Editor.cs b/UICatalog/Scenarios/Editor.cs index 8012011328..06862e29e6 100644 --- a/UICatalog/Scenarios/Editor.cs +++ b/UICatalog/Scenarios/Editor.cs @@ -743,8 +743,8 @@ private void ShowFindReplace (bool isFind = true) _findReplaceWindow.Visible = true; _findReplaceWindow.SuperView.MoveSubviewToStart (_findReplaceWindow); _tabView.SetFocus (); - _tabView.SelectedTab = isFind ? _tabView.Tabs.ToArray () [0] : _tabView.Tabs.ToArray () [1]; - _tabView.SelectedTab.View.FocusDeepest (NavigationDirection.Forward, null); + _tabView.SelectedTabIndex = isFind ? 0 : 1; + // _tabView.SelectedTabIndex.View.FocusDeepest (NavigationDirection.Forward, null); } private void CreateFindReplace () @@ -758,7 +758,7 @@ private void CreateFindReplace () _tabView.AddTab (new () { DisplayText = "Find", View = CreateFindTab () }, true); _tabView.AddTab (new () { DisplayText = "Replace", View = CreateReplaceTab () }, false); - _tabView.SelectedTabChanged += (s, e) => _tabView.SelectedTab.View.FocusDeepest (NavigationDirection.Forward, null); + //_tabView.SelectedTabChanged += (s, e) => _tabView.SelectedTabIndex.View.FocusDeepest (NavigationDirection.Forward, null); _findReplaceWindow.Add (_tabView); // _tabView.SelectedTab.View.FocusLast (null); // Hack to get the first tab to be focused diff --git a/UICatalog/Scenarios/Images.cs b/UICatalog/Scenarios/Images.cs index 6a7aebffc2..24cdcbe58d 100644 --- a/UICatalog/Scenarios/Images.cs +++ b/UICatalog/Scenarios/Images.cs @@ -293,11 +293,11 @@ private void OpenImage (object sender, CommandEventArgs e) private void ApplyShowTabViewHack () { - // TODO HACK: This hack seems to be required to make tabview actually refresh itself - _tabView.SetNeedsDraw (); - var orig = _tabView.SelectedTab; - _tabView.SelectedTab = _tabView.Tabs.Except (new [] { orig }).ElementAt (0); - _tabView.SelectedTab = orig; + //// TODO HACK: This hack seems to be required to make tabview actually refresh itself + //_tabView.SetNeedsDraw (); + //var orig = _tabView.SelectedTabIndex; + //_tabView.SelectedTabIndex = _tabView.Tabs.Except (new [] { orig }).ElementAt (0); + //_tabView.SelectedTabIndex = orig; } private void BuildBasicTab (Tab tabBasic) diff --git a/UICatalog/Scenarios/Notepad.cs b/UICatalog/Scenarios/Notepad.cs index 1a0871d557..ffb701b14e 100644 --- a/UICatalog/Scenarios/Notepad.cs +++ b/UICatalog/Scenarios/Notepad.cs @@ -98,7 +98,7 @@ public override void Main () Application.Shutdown (); } - public void Save () { Save (_focusedTabView, _focusedTabView.SelectedTab); } + public void Save () { Save (_focusedTabView, _focusedTabView.Tabs.ToArray () [_focusedTabView.SelectedTabIndex!.Value]); } public void Save (TabView tabViewToSave, Tab tabToSave) { @@ -120,7 +120,7 @@ public void Save (TabView tabViewToSave, Tab tabToSave) public bool SaveAs () { - var tab = _focusedTabView.SelectedTab as OpenedFile; + var tab = _focusedTabView.Tabs.ToArray () [_focusedTabView.SelectedTabIndex!.Value] as OpenedFile; if (tab == null) { @@ -153,7 +153,7 @@ public bool SaveAs () return true; } - private void Close () { Close (_focusedTabView, _focusedTabView.SelectedTab); } + private void Close () { Close (_focusedTabView, _focusedTabView.Tabs.ToArray()[_focusedTabView.SelectedTabIndex!.Value]); } private void Close (TabView tv, Tab tabToClose) { @@ -239,7 +239,7 @@ private TabView CreateNewTabView () { var tv = new TabView { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill () }; - tv.TabClicked += TabView_TabClicked; + // tv.TabClicked += TabView_TabClicked; tv.SelectedTabChanged += TabView_SelectedTabChanged; tv.HasFocusChanging += (s, e) => _focusedTabView = tv; @@ -320,9 +320,10 @@ private void Split (int offset, Orientation orientation, TabView sender, OpenedF private void TabView_SelectedTabChanged (object sender, TabChangedEventArgs e) { - LenShortcut.Title = $"Len:{e.NewTab?.View?.Text?.Length ?? 0}"; + Tab tab = _focusedTabView.Tabs.ToArray () [e.NewTabIndex!.Value]; + LenShortcut.Title = $"Len:{tab?.View?.Text?.Length ?? 0}"; - e.NewTab?.View?.SetFocus (); + tab?.View?.SetFocus (); } private void TabView_TabClicked (object sender, TabMouseEventArgs e) diff --git a/UICatalog/Scenarios/TabViewExample.cs b/UICatalog/Scenarios/TabViewExample.cs index 9d48c05685..530948e1a9 100644 --- a/UICatalog/Scenarios/TabViewExample.cs +++ b/UICatalog/Scenarios/TabViewExample.cs @@ -35,7 +35,7 @@ public override void Main () new ( "_Clear SelectedTab", "", - () => _tabView.SelectedTab = null + () => _tabView.SelectedTabIndex = null ), new ("_Quit", "", Quit) } @@ -129,7 +129,7 @@ public override void Main () ); } - _tabView.SelectedTab = _tabView.Tabs.First (); + _tabView.SelectedTabIndex = 0; appWindow.Add (_tabView); From a32aa87e1161fb35eb9cb0917c6a1bd3b7ca9f65 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 19 Nov 2024 10:05:25 -0700 Subject: [PATCH 2/3] Code cleanup --- Terminal.Gui/Views/{ => TabView}/Tab.cs | 0 .../{ => TabView}/TabChangedEventArgs.cs | 0 .../Views/{ => TabView}/TabMouseEventArgs.cs | 0 Terminal.Gui/Views/TabView/TabRow.cs | 203 ++++++++++++++ Terminal.Gui/Views/{ => TabView}/TabStyle.cs | 0 Terminal.Gui/Views/{ => TabView}/TabView.cs | 250 +++--------------- 6 files changed, 234 insertions(+), 219 deletions(-) rename Terminal.Gui/Views/{ => TabView}/Tab.cs (100%) rename Terminal.Gui/Views/{ => TabView}/TabChangedEventArgs.cs (100%) rename Terminal.Gui/Views/{ => TabView}/TabMouseEventArgs.cs (100%) create mode 100644 Terminal.Gui/Views/TabView/TabRow.cs rename Terminal.Gui/Views/{ => TabView}/TabStyle.cs (100%) rename Terminal.Gui/Views/{ => TabView}/TabView.cs (68%) diff --git a/Terminal.Gui/Views/Tab.cs b/Terminal.Gui/Views/TabView/Tab.cs similarity index 100% rename from Terminal.Gui/Views/Tab.cs rename to Terminal.Gui/Views/TabView/Tab.cs diff --git a/Terminal.Gui/Views/TabChangedEventArgs.cs b/Terminal.Gui/Views/TabView/TabChangedEventArgs.cs similarity index 100% rename from Terminal.Gui/Views/TabChangedEventArgs.cs rename to Terminal.Gui/Views/TabView/TabChangedEventArgs.cs diff --git a/Terminal.Gui/Views/TabMouseEventArgs.cs b/Terminal.Gui/Views/TabView/TabMouseEventArgs.cs similarity index 100% rename from Terminal.Gui/Views/TabMouseEventArgs.cs rename to Terminal.Gui/Views/TabView/TabMouseEventArgs.cs diff --git a/Terminal.Gui/Views/TabView/TabRow.cs b/Terminal.Gui/Views/TabView/TabRow.cs new file mode 100644 index 0000000000..bb68f25434 --- /dev/null +++ b/Terminal.Gui/Views/TabView/TabRow.cs @@ -0,0 +1,203 @@ +#nullable enable +namespace Terminal.Gui; + +/// +/// The host of the objects for a . Scrolls the Tabs horizontally within +/// it's Viewport (the ContentSize is set to the sum of the widths of all the Tabs). Includes two scroll buttons +/// that are made visible when needed to enable mouse scrolling across the tabs. +/// +public class TabRow : View +{ + private readonly View _leftScrollButton; + private readonly View _rightScrollButton; + + public TabRow () + { + Id = "tabRowView"; + + CanFocus = true; + Height = Dim.Auto (); + Width = Dim.Fill (); + SuperViewRendersLineCanvas = true; + + _rightScrollButton = new View + { + Id = "rightScrollButton", + X = Pos.Func (() => Viewport.X + Viewport.Width - 1), + Y = Pos.AnchorEnd (), + Width = 1, + Height = 1, + Visible = true, + Text = Glyphs.RightArrow.ToString () + }; + + _leftScrollButton = new View + { + Id = "leftScrollButton", + X = Pos.Func (() => Viewport.X), + Y = Pos.AnchorEnd (), + Width = 1, + Height = 1, + Visible = true, + Text = Glyphs.LeftArrow.ToString () + }; + + Add (_rightScrollButton, _leftScrollButton); + + Initialized += OnInitialized; + } + + private void OnInitialized (object? sender, EventArgs e) + { + if (SuperView is TabView tabView) + { + _leftScrollButton.MouseClick += (o, args) => + { + tabView.InvokeCommand (Command.ScrollLeft); + }; + _rightScrollButton.MouseClick += (o, args) => + { + tabView.InvokeCommand (Command.ScrollRight); + }; + tabView.SelectedTabChanged += TabView_SelectedTabChanged; + } + + CalcContentSize (); + } + + private void TabView_SelectedTabChanged (object? sender, TabChangedEventArgs e) + { + _selectedTabIndex = e.NewTabIndex; + CalcContentSize (); + } + + /// + public override void OnAdded (SuperViewChangedEventArgs e) + { + if (e.SubView is Tab tab) + { + MoveSubviewToEnd (_leftScrollButton); + MoveSubviewToEnd (_rightScrollButton); + + tab.HasFocusChanged += TabOnHasFocusChanged; + tab.Selecting += Tab_Selecting; + } + CalcContentSize (); + } + + private void Tab_Selecting (object? sender, CommandEventArgs e) + { + e.Cancel = RaiseSelecting (new CommandContext (Command.Select, null, data: Tabs.ToArray ().IndexOf (sender))) is true; + } + + private void TabOnHasFocusChanged (object? sender, HasFocusEventArgs e) + { + //TabView? host = SuperView as TabView; + + //if (host is null) + //{ + // return; + //} + + + //if (e is { NewFocused: Tab tab, NewValue: true }) + //{ + // e.Cancel = RaiseSInvokeCommand (Command.Select, new CommandContext () { Data = tab }) is true; + //} + } + + // TODO: This is hacky - it both calculates the content size AND changes the Adornments of the Tabs + // TODO: These concepts should separated into two methods. + public void CalcContentSize () + { + TabView? host = SuperView as TabView; + + if (host is null) + { + return; + } + + Tab? selected = null; + int topLine = host!.Style.ShowTopLine ? 1 : 0; + + Tab [] tabs = Tabs.ToArray (); + + for (int i = 0; i < tabs.Length; i++) + { + tabs [i].Height = Dim.Fill (); + if (i == 0) + { + tabs [i].X = 0; + } + else + { + tabs [i].X = Pos.Right (tabs [i - 1]); + } + + if (i == _selectedTabIndex) + { + selected = tabs [i]; + + if (host.Style.TabsOnBottom) + { + tabs [i].Border.Thickness = new Thickness (1, 0, 1, topLine); + tabs [i].Margin.Thickness = new Thickness (0, 1, 0, 0); + } + else + { + tabs [i].Border.Thickness = new Thickness (1, topLine, 1, 0); + tabs [i].Margin.Thickness = new Thickness (0, 0, 0, topLine); + } + } + else if (selected is null) + { + if (host.Style.TabsOnBottom) + { + tabs [i].Border.Thickness = new Thickness (1, 1, 0, topLine); + tabs [i].Margin.Thickness = new Thickness (0, 0, 0, 0); + } + else + { + tabs [i].Border.Thickness = new Thickness (1, topLine, 0, 1); + tabs [i].Margin.Thickness = new Thickness (0, 0, 0, 0); + } + + //tabs [i].Width = Math.Max (tabs [i].Width!.GetAnchor (0) - 1, 1); + } + else + { + if (host.Style.TabsOnBottom) + { + tabs [i].Border.Thickness = new Thickness (0, 1, 1, topLine); + tabs [i].Margin.Thickness = new Thickness (0, 0, 0, 0); + } + else + { + tabs [i].Border.Thickness = new Thickness (0, topLine, 1, 1); + tabs [i].Margin.Thickness = new Thickness (0, 0, 0, 0); + } + + //tabs [i].Width = Math.Max (tabs [i].Width!.GetAnchor (0) - 1, 1); + } + + //tabs [i].Text = toRender.TextToRender; + } + + SetContentSize (null); + Layout (Application.Screen.Size); + + var width = 0; + foreach (Tab t in tabs) + { + width += t.Frame.Width; + } + SetContentSize (new (width, Viewport.Height)); + } + + /// + /// Gets the Subviews of this View, cast to type . + /// + public IEnumerable Tabs => Subviews.Where (v => v is Tab).Cast (); + + private int? _selectedTabIndex = null; +} diff --git a/Terminal.Gui/Views/TabStyle.cs b/Terminal.Gui/Views/TabView/TabStyle.cs similarity index 100% rename from Terminal.Gui/Views/TabStyle.cs rename to Terminal.Gui/Views/TabView/TabStyle.cs diff --git a/Terminal.Gui/Views/TabView.cs b/Terminal.Gui/Views/TabView/TabView.cs similarity index 68% rename from Terminal.Gui/Views/TabView.cs rename to Terminal.Gui/Views/TabView/TabView.cs index 4e4eca828f..0c05329208 100644 --- a/Terminal.Gui/Views/TabView.cs +++ b/Terminal.Gui/Views/TabView/TabView.cs @@ -12,7 +12,7 @@ public class TabView : View, IDesignable public const uint DefaultMaxTabTextWidth = 30; /// This SubView is the 2 or 3 line control that represents the actual tabs themselves. - private readonly TabRowView _tabRowView; + private readonly TabRow _tabRow; // private TabToRender []? _tabLocations; @@ -25,9 +25,9 @@ public TabView () Width = Dim.Fill (); Height = Dim.Auto (minimumContentDim: GetTabHeight (!Style.TabsOnBottom)); - _tabRowView = new TabRowView (); - _tabRowView.Selecting += _tabRowView_Selecting; - base.Add (_tabRowView); + _tabRow = new TabRow (); + _tabRow.Selecting += _tabRowView_Selecting; + base.Add (_tabRow); ApplyStyleChanges (); @@ -87,8 +87,8 @@ public TabView () if (first > 0) { - int scroll = -_tabRowView.Tabs.ToArray () [first.Value].Frame.Width; - _tabRowView.Viewport = _tabRowView.Viewport with { X = _tabRowView.Viewport.X + scroll }; + int scroll = -_tabRow.Tabs.ToArray () [first.Value].Frame.Width; + _tabRow.Viewport = _tabRow.Viewport with { X = _tabRow.Viewport.X + scroll }; SetNeedsLayout (); FirstVisibleTabIndex--; return true; @@ -104,7 +104,7 @@ public TabView () if (last is { }) { - _tabRowView.ScrollHorizontal (_tabRowView.Tabs.ToArray () [last.Value + 1].Frame.Width); + _tabRow.ScrollHorizontal (_tabRow.Tabs.ToArray () [last.Value + 1].Frame.Width); SetNeedsLayout (); FirstVisibleTabIndex++; return true; @@ -157,7 +157,7 @@ private void _tabRowView_Selecting (object? sender, CommandEventArgs e) /// protected override void OnSubviewLayout (LayoutEventArgs args) { - _tabRowView.CalcContentSize (); + _tabRow.CalcContentSize (); } /// @@ -166,9 +166,9 @@ protected override void OnSubviewsLaidOut (LayoutEventArgs args) // hide all that can't fit var visibleTabs = GetTabsThatCanBeVisible (Viewport).ToArray (); - for (var index = 0; index < _tabRowView.Tabs.ToArray ().Length; index++) + for (var index = 0; index < _tabRow.Tabs.ToArray ().Length; index++) { - Tab tab = _tabRowView.Tabs.ToArray () [index]; + Tab tab = _tabRow.Tabs.ToArray () [index]; tab.Visible = visibleTabs.Contains (index); } } @@ -213,7 +213,7 @@ public int? SelectedTabIndex int? old = _selectedTabIndex; // Get once to avoid multiple enumerations - Tab [] tabs = _tabRowView.Tabs.ToArray (); + Tab [] tabs = _tabRow.Tabs.ToArray (); if (_selectedTabIndex is { } && tabs [_selectedTabIndex.Value].View is { }) { @@ -266,7 +266,7 @@ public TabStyle Style /// All tabs currently hosted by the control. /// - public IReadOnlyCollection Tabs => _tabRowView.Tabs.ToArray ().AsReadOnly (); + public IReadOnlyCollection Tabs => _tabRow.Tabs.ToArray ().AsReadOnly (); private int _firstVisibleTabIndex; @@ -294,17 +294,17 @@ public int FirstVisibleTabIndex public void AddTab (Tab tab, bool andSelect) { // Ok to use Subviews here instead of Tabs - if (_tabRowView.Subviews.Contains (tab)) + if (_tabRow.Subviews.Contains (tab)) { return; } // Add to the TabRowView as a subview - _tabRowView.Add (tab); + _tabRow.Add (tab); - if (_tabRowView.Tabs.Count () == 1 || andSelect) + if (_tabRow.Tabs.Count () == 1 || andSelect) { - SelectedTabIndex = _tabRowView.Tabs.Count () - 1; + SelectedTabIndex = _tabRow.Tabs.Count () - 1; EnsureSelectedTabIsVisible (); @@ -326,21 +326,21 @@ public void AddTab (Tab tab, bool andSelect) /// public void RemoveTab (Tab? tab) { - if (tab is null || !_tabRowView.Subviews.Contains (tab)) + if (tab is null || !_tabRow.Subviews.Contains (tab)) { return; } - int idx = _tabRowView.Tabs.ToArray ().IndexOf (tab); + int idx = _tabRow.Tabs.ToArray ().IndexOf (tab); if (idx == SelectedTabIndex) { SelectedTabIndex = null; } - _tabRowView.Remove (tab); + _tabRow.Remove (tab); // Get once to avoid multiple enumerations - Tab [] tabs = _tabRowView.Tabs.ToArray (); + Tab [] tabs = _tabRow.Tabs.ToArray (); if (SelectedTabIndex is null) { @@ -377,7 +377,7 @@ public void RemoveTab (Tab? tab) public void ApplyStyleChanges () { // Get once to avoid multiple enumerations - Tab [] tabs = _tabRowView.Tabs.ToArray (); + Tab [] tabs = _tabRow.Tabs.ToArray (); View? selectedView = null; @@ -396,8 +396,8 @@ public void ApplyStyleChanges () if (Style.TabsOnBottom) { - _tabRowView.Height = tabHeight; - _tabRowView.Y = Pos.AnchorEnd (); + _tabRow.Height = tabHeight; + _tabRow.Y = Pos.AnchorEnd (); if (selectedView is { }) { @@ -415,8 +415,8 @@ public void ApplyStyleChanges () else { // Tabs are along the top - _tabRowView.Height = tabHeight; - _tabRowView.Y = 0; + _tabRow.Height = tabHeight; + _tabRow.Y = 0; if (selectedView is { }) { @@ -427,7 +427,7 @@ public void ApplyStyleChanges () //move content down to make space for tabs - selectedView.Y = Pos.Bottom (_tabRowView); + selectedView.Y = Pos.Bottom (_tabRow); // Fill client area leaving space at bottom for border selectedView.Height = Dim.Fill (); @@ -446,7 +446,7 @@ public void EnsureSelectedTabIsVisible () } // Get once to avoid multiple enumerations - Tab [] tabs = _tabRowView.Tabs.ToArray (); + Tab [] tabs = _tabRow.Tabs.ToArray (); View? selectedView = tabs [SelectedTabIndex.Value].View; if (selectedView is null) @@ -475,7 +475,7 @@ public bool SwitchTabBy (int amount) { // Get once to avoid multiple enumerations - Tab [] tabs = _tabRowView.Tabs.ToArray (); + Tab [] tabs = _tabRow.Tabs.ToArray (); if (tabs.Length == 0) { @@ -524,7 +524,7 @@ private IEnumerable GetTabsThatCanBeVisible (Rectangle bounds) View? prevTab = null; // Get once to avoid multiple enumerations - Tab [] tabs = _tabRowView.Tabs.ToArray (); + Tab [] tabs = _tabRow.Tabs.ToArray (); // Starting at the first or scrolled to tab for (int i = FirstVisibleTabIndex; i < tabs.Length; i++) @@ -570,7 +570,7 @@ protected override void Dispose (bool disposing) if (disposing) { // Get once to avoid multiple enumerations - Tab [] tabs = _tabRowView.Tabs.ToArray (); + Tab [] tabs = _tabRow.Tabs.ToArray (); if (SelectedTabIndex is { }) { Remove (tabs [SelectedTabIndex.Value].View); @@ -584,194 +584,6 @@ protected override void Dispose (bool disposing) base.Dispose (disposing); } - private class TabRowView : View - { - private readonly View _leftScrollIndicator; - private readonly View _rightScrollIndicator; - - public TabRowView () - { - Id = "tabRowView"; - - CanFocus = true; - Height = Dim.Auto (); - Width = Dim.Fill (); - SuperViewRendersLineCanvas = true; - - _rightScrollIndicator = new View - { - Id = "rightScrollIndicator", - X = Pos.Func (() => Viewport.X + Viewport.Width - 1), - Y = Pos.AnchorEnd (), - Width = 1, - Height = 1, - Visible = true, - Text = Glyphs.RightArrow.ToString () - }; - - _leftScrollIndicator = new View - { - Id = "leftScrollIndicator", - X = Pos.Func (() => Viewport.X), - Y = Pos.AnchorEnd (), - Width = 1, - Height = 1, - Visible = true, - Text = Glyphs.LeftArrow.ToString () - }; - - Add (_rightScrollIndicator, _leftScrollIndicator); - - Initialized += OnInitialized; - } - - private void OnInitialized (object? sender, EventArgs e) - { - if (SuperView is TabView tabView) - { - _leftScrollIndicator.MouseClick += (o, args) => - { - tabView.InvokeCommand (Command.ScrollLeft); - }; - _rightScrollIndicator.MouseClick += (o, args) => - { - tabView.InvokeCommand (Command.ScrollRight); - }; - tabView.SelectedTabChanged += TabView_SelectedTabChanged; - } - - CalcContentSize (); - } - - private void TabView_SelectedTabChanged (object? sender, TabChangedEventArgs e) - { - _selectedTabIndex = e.NewTabIndex; - CalcContentSize (); - } - - /// - public override void OnAdded (SuperViewChangedEventArgs e) - { - if (e.SubView is Tab tab) - { - MoveSubviewToEnd (_leftScrollIndicator); - MoveSubviewToEnd (_rightScrollIndicator); - tab.HasFocusChanged += TabOnHasFocusChanged; - tab.Selecting += Tab_Selecting; - } - CalcContentSize (); - } - - private void Tab_Selecting (object? sender, CommandEventArgs e) - { - e.Cancel = RaiseSelecting (new CommandContext (Command.Select, null, data: Tabs.ToArray ().IndexOf (sender))) is true; - } - - private void TabOnHasFocusChanged (object? sender, HasFocusEventArgs e) - { - TabView? host = SuperView as TabView; - - if (host is null) - { - return; - } - - - //if (e is { NewFocused: Tab tab, NewValue: true }) - //{ - // e.Cancel = RaiseSInvokeCommand (Command.Select, new CommandContext () { Data = tab }) is true; - //} - } - - public void CalcContentSize () - { - TabView? host = SuperView as TabView; - - if (host is null) - { - return; - } - Tab? selected = null; - int topLine = host!.Style.ShowTopLine ? 1 : 0; - - Tab [] tabs = Tabs.ToArray (); - - for (int i = 0; i < tabs.Length; i++) - { - tabs [i].Height = Dim.Fill (); - if (i == 0) - { - tabs [i].X = 0; - } - else - { - tabs [i].X = Pos.Right (tabs [i - 1]); - } - - if (i == _selectedTabIndex) - { - selected = tabs [i]; - - if (host.Style.TabsOnBottom) - { - tabs [i].Border.Thickness = new Thickness (1, 0, 1, topLine); - tabs [i].Margin.Thickness = new Thickness (0, 1, 0, 0); - } - else - { - tabs [i].Border.Thickness = new Thickness (1, topLine, 1, 0); - tabs [i].Margin.Thickness = new Thickness (0, 0, 0, topLine); - } - } - else if (selected is null) - { - if (host.Style.TabsOnBottom) - { - tabs [i].Border.Thickness = new Thickness (1, 1, 0, topLine); - tabs [i].Margin.Thickness = new Thickness (0, 0, 0, 0); - } - else - { - tabs [i].Border.Thickness = new Thickness (1, topLine, 0, 1); - tabs [i].Margin.Thickness = new Thickness (0, 0, 0, 0); - } - - //tabs [i].Width = Math.Max (tabs [i].Width!.GetAnchor (0) - 1, 1); - } - else - { - if (host.Style.TabsOnBottom) - { - tabs [i].Border.Thickness = new Thickness (0, 1, 1, topLine); - tabs [i].Margin.Thickness = new Thickness (0, 0, 0, 0); - } - else - { - tabs [i].Border.Thickness = new Thickness (0, topLine, 1, 1); - tabs [i].Margin.Thickness = new Thickness (0, 0, 0, 0); - } - - //tabs [i].Width = Math.Max (tabs [i].Width!.GetAnchor (0) - 1, 1); - } - - //tabs [i].Text = toRender.TextToRender; - } - - SetContentSize (null); - Layout (Application.Screen.Size); - - var width = 0; - foreach (Tab t in tabs) - { - width += t.Frame.Width; - } - SetContentSize (new (width, Viewport.Height)); - } - - internal IEnumerable Tabs => Subviews.Where (v => v is Tab).Cast (); - - private int? _selectedTabIndex = null; - } -} +} \ No newline at end of file From f0ad458ec4407add0b8189e85a3fa90f87930b8a Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 19 Nov 2024 11:32:56 -0700 Subject: [PATCH 3/3] tweaks --- Terminal.Gui/Views/TabView/Tab.cs | 2 +- Terminal.Gui/Views/TabView/TabView.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/TabView/Tab.cs b/Terminal.Gui/Views/TabView/Tab.cs index 23bad4977c..c7afba2ce9 100644 --- a/Terminal.Gui/Views/TabView/Tab.cs +++ b/Terminal.Gui/Views/TabView/Tab.cs @@ -11,7 +11,7 @@ public class Tab : View /// Creates a new unnamed tab with no controls inside. public Tab () { - BorderStyle = LineStyle.Rounded; + BorderStyle = LineStyle.Single; CanFocus = true; TabStop = TabBehavior.TabStop; Width = Dim.Auto (DimAutoStyle.Text); diff --git a/Terminal.Gui/Views/TabView/TabView.cs b/Terminal.Gui/Views/TabView/TabView.cs index 0c05329208..5e92254561 100644 --- a/Terminal.Gui/Views/TabView/TabView.cs +++ b/Terminal.Gui/Views/TabView/TabView.cs @@ -21,6 +21,7 @@ public TabView () { CanFocus = true; TabStop = TabBehavior.TabStop; // Because TabView has focusable subviews, it must be a TabGroup + SuperViewRendersLineCanvas = true; Width = Dim.Fill (); Height = Dim.Auto (minimumContentDim: GetTabHeight (!Style.TabsOnBottom));