TabView.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777
  1. #nullable enable
  2. using System.Linq;
  3. using static Terminal.Gui.SpinnerStyle;
  4. using static Unix.Terminal.Delegates;
  5. namespace Terminal.Gui;
  6. /// <summary>Control that hosts multiple sub views, presenting a single one at once.</summary>
  7. public class TabView : View, IDesignable
  8. {
  9. /// <summary>The default <see cref="MaxTabTextWidth"/> to set on new <see cref="TabView"/> controls.</summary>
  10. public const uint DefaultMaxTabTextWidth = 30;
  11. /// <summary>This SubView is the 2 or 3 line control that represents the actual tabs themselves.</summary>
  12. private readonly TabRowView _tabRowView;
  13. // private TabToRender []? _tabLocations;
  14. /// <summary>Initializes a <see cref="TabView"/> class.</summary>
  15. public TabView ()
  16. {
  17. CanFocus = true;
  18. TabStop = TabBehavior.TabStop; // Because TabView has focusable subviews, it must be a TabGroup
  19. Width = Dim.Fill ();
  20. Height = Dim.Auto (minimumContentDim: GetTabHeight (!Style.TabsOnBottom));
  21. _tabRowView = new TabRowView ();
  22. _tabRowView.Selecting += _tabRowView_Selecting;
  23. base.Add (_tabRowView);
  24. ApplyStyleChanges ();
  25. // Things this view knows how to do
  26. AddCommand (Command.Left, () => SwitchTabBy (-1));
  27. AddCommand (Command.Right, () => SwitchTabBy (1));
  28. AddCommand (
  29. Command.LeftStart,
  30. () =>
  31. {
  32. FirstVisibleTabIndex = 0;
  33. SelectedTabIndex = 0;
  34. return true;
  35. }
  36. );
  37. AddCommand (
  38. Command.RightEnd,
  39. () =>
  40. {
  41. FirstVisibleTabIndex = Tabs.Count - 1;
  42. SelectedTabIndex = Tabs.Count - 1;
  43. return true;
  44. }
  45. );
  46. AddCommand (
  47. Command.PageDown,
  48. () =>
  49. {
  50. // FirstVisibleTabIndex += _tabLocations!.Length;
  51. SelectedTabIndex = FirstVisibleTabIndex;
  52. return true;
  53. }
  54. );
  55. AddCommand (
  56. Command.PageUp,
  57. () =>
  58. {
  59. // FirstVisibleTabIndex -= _tabLocations!.Length;
  60. SelectedTabIndex = FirstVisibleTabIndex;
  61. return true;
  62. }
  63. );
  64. AddCommand (Command.ScrollLeft, () =>
  65. {
  66. var visibleTabs = GetTabsThatCanBeVisible (Viewport).ToArray ();
  67. int? first = visibleTabs.FirstOrDefault ();
  68. if (first > 0)
  69. {
  70. int scroll = -_tabRowView.Tabs.ToArray () [first.Value].Frame.Width;
  71. _tabRowView.Viewport = _tabRowView.Viewport with { X = _tabRowView.Viewport.X + scroll };
  72. SetNeedsLayout ();
  73. FirstVisibleTabIndex--;
  74. return true;
  75. }
  76. return false;
  77. });
  78. AddCommand (Command.ScrollRight, () =>
  79. {
  80. var visibleTabs = GetTabsThatCanBeVisible (Viewport).ToArray ();
  81. int? last = visibleTabs.LastOrDefault ();
  82. if (last is { })
  83. {
  84. _tabRowView.ScrollHorizontal (_tabRowView.Tabs.ToArray () [last.Value + 1].Frame.Width);
  85. SetNeedsLayout ();
  86. FirstVisibleTabIndex++;
  87. return true;
  88. }
  89. return false;
  90. });
  91. //// Space or single-click - Raise Selecting
  92. //AddCommand (Command.Select, (ctx) =>
  93. // {
  94. // //if (RaiseSelecting (ctx) is true)
  95. // //{
  96. // // return true;
  97. // //}
  98. // if (ctx.Data is Tab tab)
  99. // {
  100. // int? current = SelectedTabIndex;
  101. // SelectedTabIndex = _tabRowView.Tabs.ToArray ().IndexOf (tab);
  102. // SetNeedsDraw ();
  103. // // e.Cancel = HasFocus;
  104. // return true;
  105. // }
  106. // return false;
  107. // });
  108. // Default keybindings for this view
  109. KeyBindings.Add (Key.CursorLeft, Command.Left);
  110. KeyBindings.Add (Key.CursorRight, Command.Right);
  111. KeyBindings.Add (Key.Home, Command.LeftStart);
  112. KeyBindings.Add (Key.End, Command.RightEnd);
  113. KeyBindings.Add (Key.PageDown, Command.PageDown);
  114. KeyBindings.Add (Key.PageUp, Command.PageUp);
  115. }
  116. private void _tabRowView_Selecting (object? sender, CommandEventArgs e)
  117. {
  118. if (e.Context.Data is int tabIndex)
  119. {
  120. int? current = SelectedTabIndex;
  121. SelectedTabIndex = tabIndex;
  122. Layout ();
  123. e.Cancel = true;
  124. }
  125. }
  126. /// <inheritdoc />
  127. protected override void OnSubviewLayout (LayoutEventArgs args)
  128. {
  129. _tabRowView.CalcContentSize ();
  130. }
  131. /// <inheritdoc />
  132. protected override void OnSubviewsLaidOut (LayoutEventArgs args)
  133. {
  134. // hide all that can't fit
  135. var visibleTabs = GetTabsThatCanBeVisible (Viewport).ToArray ();
  136. for (var index = 0; index < _tabRowView.Tabs.ToArray ().Length; index++)
  137. {
  138. Tab tab = _tabRowView.Tabs.ToArray () [index];
  139. tab.Visible = visibleTabs.Contains (index);
  140. }
  141. }
  142. /// <inheritdoc />
  143. public bool EnableForDesign ()
  144. {
  145. AddTab (new () { Text = "Tab_1", Id = "tab1", View = new Label { Text = "Label in Tab1" } }, false);
  146. AddTab (new () { Text = "Tab _2", Id = "tab2", View = new TextField { Text = "TextField in Tab2", Width = 10 } }, false);
  147. AddTab (new () { Text = "Tab _Three", Id = "tab3", View = new Label { Text = "Label in Tab3" } }, false);
  148. AddTab (new () { Text = "Tab _Quattro", Id = "tab4", View = new TextField { Text = "TextField in Tab4", Width = 10 } }, false);
  149. return true;
  150. }
  151. /// <summary>
  152. /// The maximum number of characters to render in a Tab header. This prevents one long tab from pushing out all
  153. /// the others.
  154. /// </summary>
  155. public uint MaxTabTextWidth { get; set; } = DefaultMaxTabTextWidth;
  156. private int? _selectedTabIndex;
  157. /// <summary>The currently selected member of <see cref="Tabs"/> chosen by the user.</summary>
  158. /// <value></value>
  159. public int? SelectedTabIndex
  160. {
  161. get => _selectedTabIndex;
  162. set
  163. {
  164. // If value is outside the range of Tabs, throw an exception
  165. if (value < 0 || value >= Tabs.Count)
  166. {
  167. throw new ArgumentOutOfRangeException (nameof (value), value, @"SelectedTab the range of Tabs.");
  168. }
  169. if (value == _selectedTabIndex)
  170. {
  171. return;
  172. }
  173. int? old = _selectedTabIndex;
  174. // Get once to avoid multiple enumerations
  175. Tab [] tabs = _tabRowView.Tabs.ToArray ();
  176. if (_selectedTabIndex is { } && tabs [_selectedTabIndex.Value].View is { })
  177. {
  178. Remove (tabs [_selectedTabIndex.Value].View);
  179. }
  180. _selectedTabIndex = value;
  181. if (_selectedTabIndex is { } && tabs [_selectedTabIndex.Value].View is { })
  182. {
  183. Add (tabs [_selectedTabIndex.Value].View);
  184. }
  185. EnsureSelectedTabIsVisible ();
  186. if (_selectedTabIndex is { })
  187. {
  188. ApplyStyleChanges ();
  189. if (HasFocus)
  190. {
  191. tabs [_selectedTabIndex.Value].View.SetFocus ();
  192. }
  193. }
  194. OnSelectedTabIndexChanged (old, _selectedTabIndex!);
  195. SelectedTabChanged?.Invoke (this, new TabChangedEventArgs (old, _selectedTabIndex));
  196. SetNeedsLayout ();
  197. }
  198. }
  199. private TabStyle _style = new ();
  200. /// <summary>Render choices for how to display tabs. After making changes, call <see cref="ApplyStyleChanges()"/>.</summary>
  201. /// <value></value>
  202. public TabStyle Style
  203. {
  204. get => _style;
  205. set
  206. {
  207. if (_style == value)
  208. {
  209. return;
  210. }
  211. _style = value;
  212. SetNeedsLayout ();
  213. }
  214. }
  215. /// <summary>All tabs currently hosted by the control.</summary>
  216. /// <value></value>
  217. public IReadOnlyCollection<Tab> Tabs => _tabRowView.Tabs.ToArray ().AsReadOnly ();
  218. private int _firstVisibleTabIndex;
  219. /// <summary>Gets or sets the index of first visible tab. This enables horizontal scrolling of the tabs.</summary>
  220. /// <remarks>
  221. /// <para>
  222. /// 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
  223. /// it will be set to the last tab index.
  224. /// </para>
  225. /// </remarks>
  226. public int FirstVisibleTabIndex
  227. {
  228. get => _firstVisibleTabIndex;
  229. set
  230. {
  231. _firstVisibleTabIndex = Math.Max (Math.Min (value, Tabs.Count - 1), 0);
  232. ;
  233. SetNeedsLayout ();
  234. }
  235. }
  236. /// <summary>Adds the given <paramref name="tab"/> to <see cref="Tabs"/>.</summary>
  237. /// <param name="tab"></param>
  238. /// <param name="andSelect">True to make the newly added Tab the <see cref="SelectedTabIndex"/>.</param>
  239. public void AddTab (Tab tab, bool andSelect)
  240. {
  241. // Ok to use Subviews here instead of Tabs
  242. if (_tabRowView.Subviews.Contains (tab))
  243. {
  244. return;
  245. }
  246. // Add to the TabRowView as a subview
  247. _tabRowView.Add (tab);
  248. if (_tabRowView.Tabs.Count () == 1 || andSelect)
  249. {
  250. SelectedTabIndex = _tabRowView.Tabs.Count () - 1;
  251. EnsureSelectedTabIsVisible ();
  252. if (HasFocus)
  253. {
  254. tab.View?.SetFocus ();
  255. }
  256. }
  257. ApplyStyleChanges ();
  258. SetNeedsLayout ();
  259. }
  260. /// <summary>
  261. /// Removes the given <paramref name="tab"/> from <see cref="Tabs"/>. Caller is responsible for disposing the
  262. /// tab's hosted <see cref="Tab.View"/> if appropriate.
  263. /// </summary>
  264. /// <param name="tab"></param>
  265. public void RemoveTab (Tab? tab)
  266. {
  267. if (tab is null || !_tabRowView.Subviews.Contains (tab))
  268. {
  269. return;
  270. }
  271. int idx = _tabRowView.Tabs.ToArray ().IndexOf (tab);
  272. if (idx == SelectedTabIndex)
  273. {
  274. SelectedTabIndex = null;
  275. }
  276. _tabRowView.Remove (tab);
  277. // Get once to avoid multiple enumerations
  278. Tab [] tabs = _tabRowView.Tabs.ToArray ();
  279. if (SelectedTabIndex is null)
  280. {
  281. // Either no tab was previously selected or the selected tab was removed
  282. // select the tab closest to the one that disappeared
  283. int toSelect = Math.Max (idx - 1, 0);
  284. if (toSelect < tabs.Length)
  285. {
  286. SelectedTabIndex = toSelect;
  287. }
  288. else
  289. {
  290. SelectedTabIndex = tabs.Length - 1;
  291. }
  292. }
  293. if (SelectedTabIndex > tabs.Length - 1)
  294. {
  295. // Removing the tab, caused the selected tab to be out of range
  296. SelectedTabIndex = tabs.Length - 1;
  297. }
  298. EnsureSelectedTabIsVisible ();
  299. SetNeedsLayout ();
  300. }
  301. /// <summary>
  302. /// Applies the settings in <see cref="Style"/>. This can change the dimensions of
  303. /// <see cref="Tab.View"/> (for rendering the selected tab's content). This method includes a call to
  304. /// <see cref="View.SetNeedsDraw()"/>.
  305. /// </summary>
  306. public void ApplyStyleChanges ()
  307. {
  308. // Get once to avoid multiple enumerations
  309. Tab [] tabs = _tabRowView.Tabs.ToArray ();
  310. View? selectedView = null;
  311. if (SelectedTabIndex is { })
  312. {
  313. selectedView = tabs [SelectedTabIndex.Value].View;
  314. }
  315. if (selectedView is { })
  316. {
  317. selectedView.BorderStyle = Style.ShowBorder ? LineStyle.Single : LineStyle.None;
  318. selectedView.Width = Dim.Fill ();
  319. }
  320. int tabHeight = GetTabHeight (!Style.TabsOnBottom);
  321. if (Style.TabsOnBottom)
  322. {
  323. _tabRowView.Height = tabHeight;
  324. _tabRowView.Y = Pos.AnchorEnd ();
  325. if (selectedView is { })
  326. {
  327. // Tabs are along the bottom so just dodge the border
  328. if (Style.ShowBorder && selectedView?.Border is { })
  329. {
  330. selectedView.Border.Thickness = new Thickness (1, 1, 1, 0);
  331. }
  332. // Fill client area leaving space at bottom for tabs
  333. selectedView!.Y = 0;
  334. selectedView.Height = Dim.Fill (tabHeight);
  335. }
  336. }
  337. else
  338. {
  339. // Tabs are along the top
  340. _tabRowView.Height = tabHeight;
  341. _tabRowView.Y = 0;
  342. if (selectedView is { })
  343. {
  344. if (Style.ShowBorder && selectedView.Border is { })
  345. {
  346. selectedView.Border.Thickness = new Thickness (1, 0, 1, 1);
  347. }
  348. //move content down to make space for tabs
  349. selectedView.Y = Pos.Bottom (_tabRowView);
  350. // Fill client area leaving space at bottom for border
  351. selectedView.Height = Dim.Fill ();
  352. }
  353. }
  354. SetNeedsLayout ();
  355. }
  356. /// <summary>Updates <see cref="FirstVisibleTabIndex"/> to ensure that <see cref="SelectedTabIndex"/> is visible.</summary>
  357. public void EnsureSelectedTabIsVisible ()
  358. {
  359. if (SelectedTabIndex is null)
  360. {
  361. return;
  362. }
  363. // Get once to avoid multiple enumerations
  364. Tab [] tabs = _tabRowView.Tabs.ToArray ();
  365. View? selectedView = tabs [SelectedTabIndex.Value].View;
  366. if (selectedView is null)
  367. {
  368. return;
  369. }
  370. // if current viewport does not include the selected tab
  371. if (!GetTabsThatCanBeVisible (Viewport).Any (r => Equals (SelectedTabIndex.Value, r)))
  372. {
  373. // Set scroll offset so the first tab rendered is the
  374. FirstVisibleTabIndex = Math.Max (0, SelectedTabIndex.Value);
  375. }
  376. }
  377. /// <summary>Event for when <see cref="SelectedTabIndex"/> changes.</summary>
  378. public event EventHandler<TabChangedEventArgs>? SelectedTabChanged;
  379. /// <summary>
  380. /// Changes the <see cref="SelectedTabIndex"/> by the given <paramref name="amount"/>. Positive for right, negative for
  381. /// left. If no tab is currently selected then the first tab will become selected.
  382. /// </summary>
  383. /// <param name="amount"></param>
  384. /// <returns><see langword="true"/> if a change was made.</returns>
  385. public bool SwitchTabBy (int amount)
  386. {
  387. // Get once to avoid multiple enumerations
  388. Tab [] tabs = _tabRowView.Tabs.ToArray ();
  389. if (tabs.Length == 0)
  390. {
  391. return false;
  392. }
  393. int? currentIdx = SelectedTabIndex;
  394. // if there is only one tab anyway or nothing is selected
  395. if (tabs.Length == 1)
  396. {
  397. SelectedTabIndex = 0;
  398. return SelectedTabIndex != currentIdx;
  399. }
  400. // Currently selected tab has vanished!
  401. if (currentIdx is null)
  402. {
  403. SelectedTabIndex = 0;
  404. return true;
  405. }
  406. int newIdx = Math.Max (0, Math.Min (currentIdx.Value + amount, tabs.Length - 1));
  407. if (newIdx == currentIdx)
  408. {
  409. return false;
  410. }
  411. SelectedTabIndex = newIdx;
  412. return true;
  413. }
  414. /// <summary>Called when the <see cref="SelectedTabIndex"/> has changed.</summary>
  415. protected virtual void OnSelectedTabIndexChanged (int? oldTabIndex, int? newTabIndex) { }
  416. /// <summary>Returns which tabs will be visible given the dimensions of the TabView, which tab is selected, and how the tabs have been scrolled.</summary>
  417. /// <paramref name="bounds">Same as this.Frame.</paramref>
  418. /// <returns></returns>
  419. private IEnumerable<int> GetTabsThatCanBeVisible (Rectangle bounds)
  420. {
  421. var curWidth = 1;
  422. View? prevTab = null;
  423. // Get once to avoid multiple enumerations
  424. Tab [] tabs = _tabRowView.Tabs.ToArray ();
  425. // Starting at the first or scrolled to tab
  426. for (int i = FirstVisibleTabIndex; i < tabs.Length; i++)
  427. {
  428. if (curWidth >= bounds.Width)
  429. {
  430. break;
  431. }
  432. if (curWidth + tabs [i].Frame.Width < bounds.Width)
  433. {
  434. yield return i;
  435. }
  436. curWidth += tabs [i].Frame.Width;
  437. }
  438. }
  439. /// <summary>
  440. /// Returns the number of rows occupied by rendering the tabs, this depends on <see cref="TabStyle.ShowTopLine"/>
  441. /// and can be 0 (e.g. if <see cref="TabStyle.TabsOnBottom"/> and you ask for <paramref name="top"/>).
  442. /// </summary>
  443. /// <param name="top">True to measure the space required at the top of the control, false to measure space at the bottom.</param>
  444. /// .
  445. /// <returns></returns>
  446. private int GetTabHeight (bool top)
  447. {
  448. if (top && Style.TabsOnBottom)
  449. {
  450. return 0;
  451. }
  452. if (!top && !Style.TabsOnBottom)
  453. {
  454. return 0;
  455. }
  456. return Style.ShowTopLine ? 3 : 2;
  457. }
  458. /// <inheritdoc />
  459. protected override void Dispose (bool disposing)
  460. {
  461. if (disposing)
  462. {
  463. // Get once to avoid multiple enumerations
  464. Tab [] tabs = _tabRowView.Tabs.ToArray ();
  465. if (SelectedTabIndex is { })
  466. {
  467. Remove (tabs [SelectedTabIndex.Value].View);
  468. }
  469. foreach (Tab tab in tabs)
  470. {
  471. tab.View?.Dispose ();
  472. tab.View = null;
  473. }
  474. };
  475. base.Dispose (disposing);
  476. }
  477. private class TabRowView : View
  478. {
  479. private readonly View _leftScrollIndicator;
  480. private readonly View _rightScrollIndicator;
  481. public TabRowView ()
  482. {
  483. Id = "tabRowView";
  484. CanFocus = true;
  485. Height = Dim.Auto ();
  486. Width = Dim.Fill ();
  487. SuperViewRendersLineCanvas = true;
  488. _rightScrollIndicator = new View
  489. {
  490. Id = "rightScrollIndicator",
  491. X = Pos.Func (() => Viewport.X + Viewport.Width - 1),
  492. Y = Pos.AnchorEnd (),
  493. Width = 1,
  494. Height = 1,
  495. Visible = true,
  496. Text = Glyphs.RightArrow.ToString ()
  497. };
  498. _leftScrollIndicator = new View
  499. {
  500. Id = "leftScrollIndicator",
  501. X = Pos.Func (() => Viewport.X),
  502. Y = Pos.AnchorEnd (),
  503. Width = 1,
  504. Height = 1,
  505. Visible = true,
  506. Text = Glyphs.LeftArrow.ToString ()
  507. };
  508. Add (_rightScrollIndicator, _leftScrollIndicator);
  509. Initialized += OnInitialized;
  510. }
  511. private void OnInitialized (object? sender, EventArgs e)
  512. {
  513. if (SuperView is TabView tabView)
  514. {
  515. _leftScrollIndicator.MouseClick += (o, args) =>
  516. {
  517. tabView.InvokeCommand (Command.ScrollLeft);
  518. };
  519. _rightScrollIndicator.MouseClick += (o, args) =>
  520. {
  521. tabView.InvokeCommand (Command.ScrollRight);
  522. };
  523. tabView.SelectedTabChanged += TabView_SelectedTabChanged;
  524. }
  525. CalcContentSize ();
  526. }
  527. private void TabView_SelectedTabChanged (object? sender, TabChangedEventArgs e)
  528. {
  529. _selectedTabIndex = e.NewTabIndex;
  530. CalcContentSize ();
  531. }
  532. /// <inheritdoc />
  533. public override void OnAdded (SuperViewChangedEventArgs e)
  534. {
  535. if (e.SubView is Tab tab)
  536. {
  537. MoveSubviewToEnd (_leftScrollIndicator);
  538. MoveSubviewToEnd (_rightScrollIndicator);
  539. tab.HasFocusChanged += TabOnHasFocusChanged;
  540. tab.Selecting += Tab_Selecting;
  541. }
  542. CalcContentSize ();
  543. }
  544. private void Tab_Selecting (object? sender, CommandEventArgs e)
  545. {
  546. e.Cancel = RaiseSelecting (new CommandContext (Command.Select, null, data: Tabs.ToArray ().IndexOf (sender))) is true;
  547. }
  548. private void TabOnHasFocusChanged (object? sender, HasFocusEventArgs e)
  549. {
  550. TabView? host = SuperView as TabView;
  551. if (host is null)
  552. {
  553. return;
  554. }
  555. //if (e is { NewFocused: Tab tab, NewValue: true })
  556. //{
  557. // e.Cancel = RaiseSInvokeCommand (Command.Select, new CommandContext () { Data = tab }) is true;
  558. //}
  559. }
  560. public void CalcContentSize ()
  561. {
  562. TabView? host = SuperView as TabView;
  563. if (host is null)
  564. {
  565. return;
  566. }
  567. Tab? selected = null;
  568. int topLine = host!.Style.ShowTopLine ? 1 : 0;
  569. Tab [] tabs = Tabs.ToArray ();
  570. for (int i = 0; i < tabs.Length; i++)
  571. {
  572. tabs [i].Height = Dim.Fill ();
  573. if (i == 0)
  574. {
  575. tabs [i].X = 0;
  576. }
  577. else
  578. {
  579. tabs [i].X = Pos.Right (tabs [i - 1]);
  580. }
  581. if (i == _selectedTabIndex)
  582. {
  583. selected = tabs [i];
  584. if (host.Style.TabsOnBottom)
  585. {
  586. tabs [i].Border.Thickness = new Thickness (1, 0, 1, topLine);
  587. tabs [i].Margin.Thickness = new Thickness (0, 1, 0, 0);
  588. }
  589. else
  590. {
  591. tabs [i].Border.Thickness = new Thickness (1, topLine, 1, 0);
  592. tabs [i].Margin.Thickness = new Thickness (0, 0, 0, topLine);
  593. }
  594. }
  595. else if (selected is null)
  596. {
  597. if (host.Style.TabsOnBottom)
  598. {
  599. tabs [i].Border.Thickness = new Thickness (1, 1, 0, topLine);
  600. tabs [i].Margin.Thickness = new Thickness (0, 0, 0, 0);
  601. }
  602. else
  603. {
  604. tabs [i].Border.Thickness = new Thickness (1, topLine, 0, 1);
  605. tabs [i].Margin.Thickness = new Thickness (0, 0, 0, 0);
  606. }
  607. //tabs [i].Width = Math.Max (tabs [i].Width!.GetAnchor (0) - 1, 1);
  608. }
  609. else
  610. {
  611. if (host.Style.TabsOnBottom)
  612. {
  613. tabs [i].Border.Thickness = new Thickness (0, 1, 1, topLine);
  614. tabs [i].Margin.Thickness = new Thickness (0, 0, 0, 0);
  615. }
  616. else
  617. {
  618. tabs [i].Border.Thickness = new Thickness (0, topLine, 1, 1);
  619. tabs [i].Margin.Thickness = new Thickness (0, 0, 0, 0);
  620. }
  621. //tabs [i].Width = Math.Max (tabs [i].Width!.GetAnchor (0) - 1, 1);
  622. }
  623. //tabs [i].Text = toRender.TextToRender;
  624. }
  625. SetContentSize (null);
  626. Layout (Application.Screen.Size);
  627. var width = 0;
  628. foreach (Tab t in tabs)
  629. {
  630. width += t.Frame.Width;
  631. }
  632. SetContentSize (new (width, Viewport.Height));
  633. }
  634. internal IEnumerable<Tab> Tabs => Subviews.Where (v => v is Tab).Cast<Tab> ();
  635. private int? _selectedTabIndex = null;
  636. }
  637. }