TabView.cs 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366
  1. using System.Diagnostics;
  2. namespace Terminal.Gui;
  3. /// <summary>Control that hosts multiple sub views, presenting a single one at once.</summary>
  4. public class TabView : View
  5. {
  6. /// <summary>The default <see cref="MaxTabTextWidth"/> to set on new <see cref="TabView"/> controls.</summary>
  7. public const uint DefaultMaxTabTextWidth = 30;
  8. /// <summary>
  9. /// This sub view is the main client area of the current tab. It hosts the <see cref="Tab.View"/> of the tab, the
  10. /// <see cref="SelectedTab"/>.
  11. /// </summary>
  12. private readonly View _contentView;
  13. private readonly List<Tab> _tabs = new ();
  14. /// <summary>This sub view is the 2 or 3 line control that represents the actual tabs themselves.</summary>
  15. private readonly TabRowView _tabsBar;
  16. private Tab _selectedTab;
  17. private TabToRender [] _tabLocations;
  18. private int _tabScrollOffset;
  19. /// <summary>Initializes a <see cref="TabView"/> class.</summary>
  20. public TabView ()
  21. {
  22. CanFocus = true;
  23. TabStop = TabBehavior.TabStop; // Because TabView has focusable subviews, it must be a TabGroup
  24. _tabsBar = new TabRowView (this);
  25. _contentView = new View ()
  26. {
  27. //Id = "TabView._contentView",
  28. };
  29. ApplyStyleChanges ();
  30. base.Add (_tabsBar);
  31. base.Add (_contentView);
  32. // Things this view knows how to do
  33. AddCommand (Command.Left, () => SwitchTabBy (-1));
  34. AddCommand (Command.Right, () => SwitchTabBy (1));
  35. AddCommand (
  36. Command.LeftStart,
  37. () =>
  38. {
  39. TabScrollOffset = 0;
  40. SelectedTab = Tabs.FirstOrDefault ();
  41. return true;
  42. }
  43. );
  44. AddCommand (
  45. Command.RightEnd,
  46. () =>
  47. {
  48. TabScrollOffset = Tabs.Count - 1;
  49. SelectedTab = Tabs.LastOrDefault ();
  50. return true;
  51. }
  52. );
  53. AddCommand (
  54. Command.PageDown,
  55. () =>
  56. {
  57. TabScrollOffset += _tabLocations.Length;
  58. SelectedTab = Tabs.ElementAt (TabScrollOffset);
  59. return true;
  60. }
  61. );
  62. AddCommand (
  63. Command.PageUp,
  64. () =>
  65. {
  66. TabScrollOffset -= _tabLocations.Length;
  67. SelectedTab = Tabs.ElementAt (TabScrollOffset);
  68. return true;
  69. }
  70. );
  71. // Default keybindings for this view
  72. KeyBindings.Add (Key.CursorLeft, Command.Left);
  73. KeyBindings.Add (Key.CursorRight, Command.Right);
  74. KeyBindings.Add (Key.Home, Command.LeftStart);
  75. KeyBindings.Add (Key.End, Command.RightEnd);
  76. KeyBindings.Add (Key.PageDown, Command.PageDown);
  77. KeyBindings.Add (Key.PageUp, Command.PageUp);
  78. }
  79. /// <summary>
  80. /// The maximum number of characters to render in a Tab header. This prevents one long tab from pushing out all
  81. /// the others.
  82. /// </summary>
  83. public uint MaxTabTextWidth { get; set; } = DefaultMaxTabTextWidth;
  84. /// <summary>The currently selected member of <see cref="Tabs"/> chosen by the user.</summary>
  85. /// <value></value>
  86. public Tab SelectedTab
  87. {
  88. get => _selectedTab;
  89. set
  90. {
  91. UnSetCurrentTabs ();
  92. Tab old = _selectedTab;
  93. if (_selectedTab is { })
  94. {
  95. if (_selectedTab.View is { })
  96. {
  97. // remove old content
  98. _contentView.Remove (_selectedTab.View);
  99. }
  100. }
  101. _selectedTab = value;
  102. if (value is { })
  103. {
  104. // add new content
  105. if (_selectedTab.View is { })
  106. {
  107. _contentView.Add (_selectedTab.View);
  108. // _contentView.Id = $"_contentView for {_selectedTab.DisplayText}";
  109. }
  110. }
  111. _contentView.CanFocus = _contentView.Subviews.Count (v => v.CanFocus) > 0;
  112. EnsureSelectedTabIsVisible ();
  113. if (old != value)
  114. {
  115. if (old?.HasFocus == true)
  116. {
  117. SelectedTab?.SetFocus ();
  118. }
  119. OnSelectedTabChanged (old, value);
  120. }
  121. }
  122. }
  123. /// <summary>Render choices for how to display tabs. After making changes, call <see cref="ApplyStyleChanges()"/>.</summary>
  124. /// <value></value>
  125. public TabStyle Style { get; set; } = new ();
  126. /// <summary>All tabs currently hosted by the control.</summary>
  127. /// <value></value>
  128. public IReadOnlyCollection<Tab> Tabs => _tabs.AsReadOnly ();
  129. /// <summary>When there are too many tabs to render, this indicates the first tab to render on the screen.</summary>
  130. /// <value></value>
  131. public int TabScrollOffset
  132. {
  133. get => _tabScrollOffset;
  134. set => _tabScrollOffset = EnsureValidScrollOffsets (value);
  135. }
  136. /// <summary>Adds the given <paramref name="tab"/> to <see cref="Tabs"/>.</summary>
  137. /// <param name="tab"></param>
  138. /// <param name="andSelect">True to make the newly added Tab the <see cref="SelectedTab"/>.</param>
  139. public void AddTab (Tab tab, bool andSelect)
  140. {
  141. if (_tabs.Contains (tab))
  142. {
  143. return;
  144. }
  145. _tabs.Add (tab);
  146. _tabsBar.Add (tab);
  147. if (SelectedTab is null || andSelect)
  148. {
  149. SelectedTab = tab;
  150. EnsureSelectedTabIsVisible ();
  151. tab.View?.SetFocus ();
  152. }
  153. SetNeedsDisplay ();
  154. }
  155. /// <summary>
  156. /// Updates the control to use the latest state settings in <see cref="Style"/>. This can change the size of the
  157. /// client area of the tab (for rendering the selected tab's content). This method includes a call to
  158. /// <see cref="View.SetNeedsDisplay()"/>.
  159. /// </summary>
  160. public void ApplyStyleChanges ()
  161. {
  162. _contentView.BorderStyle = Style.ShowBorder ? LineStyle.Single : LineStyle.None;
  163. _contentView.Width = Dim.Fill ();
  164. if (Style.TabsOnBottom)
  165. {
  166. // Tabs are along the bottom so just dodge the border
  167. if (Style.ShowBorder)
  168. {
  169. _contentView.Border.Thickness = new Thickness (1, 1, 1, 0);
  170. }
  171. _contentView.Y = 0;
  172. int tabHeight = GetTabHeight (false);
  173. // Fill client area leaving space at bottom for tabs
  174. _contentView.Height = Dim.Fill (tabHeight);
  175. _tabsBar.Height = tabHeight;
  176. _tabsBar.Y = Pos.Bottom (_contentView);
  177. }
  178. else
  179. {
  180. // Tabs are along the top
  181. if (Style.ShowBorder)
  182. {
  183. _contentView.Border.Thickness = new Thickness (1, 0, 1, 1);
  184. }
  185. _tabsBar.Y = 0;
  186. int tabHeight = GetTabHeight (true);
  187. //move content down to make space for tabs
  188. _contentView.Y = Pos.Bottom (_tabsBar) ;
  189. // Fill client area leaving space at bottom for border
  190. _contentView.Height = Dim.Fill ();
  191. // The top tab should be 2 or 3 rows high and on the top
  192. _tabsBar.Height = tabHeight;
  193. // Should be able to just use 0 but switching between top/bottom tabs repeatedly breaks in ValidatePosDim if just using the absolute value 0
  194. }
  195. if (IsInitialized)
  196. {
  197. LayoutSubviews ();
  198. }
  199. SetNeedsDisplay ();
  200. }
  201. /// <summary>Updates <see cref="TabScrollOffset"/> to ensure that <see cref="SelectedTab"/> is visible.</summary>
  202. public void EnsureSelectedTabIsVisible ()
  203. {
  204. if (!IsInitialized || SelectedTab is null)
  205. {
  206. return;
  207. }
  208. // if current viewport does not include the selected tab
  209. if (!CalculateViewport (Viewport).Any (r => Equals (SelectedTab, r.Tab)))
  210. {
  211. // Set scroll offset so the first tab rendered is the
  212. TabScrollOffset = Math.Max (0, Tabs.IndexOf (SelectedTab));
  213. }
  214. }
  215. /// <summary>Updates <see cref="TabScrollOffset"/> to be a valid index of <see cref="Tabs"/>.</summary>
  216. /// <param name="value">The value to validate.</param>
  217. /// <remarks>Changes will not be immediately visible in the display until you call <see cref="View.SetNeedsDisplay()"/>.</remarks>
  218. /// <returns>The valid <see cref="TabScrollOffset"/> for the given value.</returns>
  219. public int EnsureValidScrollOffsets (int value) { return Math.Max (Math.Min (value, Tabs.Count - 1), 0); }
  220. /// <inheritdoc/>
  221. public override void OnDrawContent (Rectangle viewport)
  222. {
  223. Driver.SetAttribute (GetNormalColor ());
  224. if (Tabs.Any ())
  225. {
  226. Rectangle savedClip = SetClip ();
  227. _tabsBar.OnDrawContent (viewport);
  228. _contentView.SetNeedsDisplay ();
  229. _contentView.Draw ();
  230. Driver.Clip = savedClip;
  231. }
  232. }
  233. /// <inheritdoc/>
  234. public override void OnDrawContentComplete (Rectangle viewport) { _tabsBar.OnDrawContentComplete (viewport); }
  235. /// <summary>
  236. /// Removes the given <paramref name="tab"/> from <see cref="Tabs"/>. Caller is responsible for disposing the
  237. /// tab's hosted <see cref="Tab.View"/> if appropriate.
  238. /// </summary>
  239. /// <param name="tab"></param>
  240. public void RemoveTab (Tab tab)
  241. {
  242. if (tab is null || !_tabs.Contains (tab))
  243. {
  244. return;
  245. }
  246. // what tab was selected before closing
  247. int idx = _tabs.IndexOf (tab);
  248. _tabs.Remove (tab);
  249. // if the currently selected tab is no longer a member of Tabs
  250. if (SelectedTab is null || !Tabs.Contains (SelectedTab))
  251. {
  252. // select the tab closest to the one that disappeared
  253. int toSelect = Math.Max (idx - 1, 0);
  254. if (toSelect < Tabs.Count)
  255. {
  256. SelectedTab = Tabs.ElementAt (toSelect);
  257. }
  258. else
  259. {
  260. SelectedTab = Tabs.LastOrDefault ();
  261. }
  262. }
  263. EnsureSelectedTabIsVisible ();
  264. SetNeedsDisplay ();
  265. }
  266. /// <summary>Event for when <see cref="SelectedTab"/> changes.</summary>
  267. public event EventHandler<TabChangedEventArgs> SelectedTabChanged;
  268. /// <summary>
  269. /// Changes the <see cref="SelectedTab"/> by the given <paramref name="amount"/>. Positive for right, negative for
  270. /// left. If no tab is currently selected then the first tab will become selected.
  271. /// </summary>
  272. /// <param name="amount"></param>
  273. public bool SwitchTabBy (int amount)
  274. {
  275. if (Tabs.Count == 0)
  276. {
  277. return false;
  278. }
  279. // if there is only one tab anyway or nothing is selected
  280. if (Tabs.Count == 1 || SelectedTab is null)
  281. {
  282. SelectedTab = Tabs.ElementAt (0);
  283. SetNeedsDisplay ();
  284. return SelectedTab is { };
  285. }
  286. int currentIdx = Tabs.IndexOf (SelectedTab);
  287. // Currently selected tab has vanished!
  288. if (currentIdx == -1)
  289. {
  290. SelectedTab = Tabs.ElementAt (0);
  291. SetNeedsDisplay ();
  292. return true;
  293. }
  294. int newIdx = Math.Max (0, Math.Min (currentIdx + amount, Tabs.Count - 1));
  295. if (newIdx == currentIdx)
  296. {
  297. return false;
  298. }
  299. SelectedTab = _tabs [newIdx];
  300. SetNeedsDisplay ();
  301. EnsureSelectedTabIsVisible ();
  302. return true;
  303. }
  304. /// <summary>
  305. /// Event fired when a <see cref="Tab"/> is clicked. Can be used to cancel navigation, show context menu (e.g. on
  306. /// right click) etc.
  307. /// </summary>
  308. public event EventHandler<TabMouseEventArgs> TabClicked;
  309. /// <summary>Disposes the control and all <see cref="Tabs"/>.</summary>
  310. /// <param name="disposing"></param>
  311. protected override void Dispose (bool disposing)
  312. {
  313. base.Dispose (disposing);
  314. // The selected tab will automatically be disposed but
  315. // any tabs not visible will need to be manually disposed
  316. foreach (Tab tab in Tabs)
  317. {
  318. if (!Equals (SelectedTab, tab))
  319. {
  320. tab.View?.Dispose ();
  321. }
  322. }
  323. }
  324. /// <summary>Raises the <see cref="SelectedTabChanged"/> event.</summary>
  325. protected virtual void OnSelectedTabChanged (Tab oldTab, Tab newTab)
  326. {
  327. SelectedTabChanged?.Invoke (this, new TabChangedEventArgs (oldTab, newTab));
  328. }
  329. /// <summary>Returns which tabs to render at each x location.</summary>
  330. /// <returns></returns>
  331. private IEnumerable<TabToRender> CalculateViewport (Rectangle bounds)
  332. {
  333. UnSetCurrentTabs ();
  334. var i = 1;
  335. View prevTab = null;
  336. // Starting at the first or scrolled to tab
  337. foreach (Tab tab in Tabs.Skip (TabScrollOffset))
  338. {
  339. if (prevTab is { })
  340. {
  341. tab.X = Pos.Right (prevTab);
  342. }
  343. else
  344. {
  345. tab.X = 0;
  346. }
  347. tab.Y = 0;
  348. // while there is space for the tab
  349. int tabTextWidth = tab.DisplayText.EnumerateRunes ().Sum (c => c.GetColumns ());
  350. string text = tab.DisplayText;
  351. // The maximum number of characters to use for the tab name as specified
  352. // by the user (MaxTabTextWidth). But not more than the width of the view
  353. // or we won't even be able to render a single tab!
  354. long maxWidth = Math.Max (0, Math.Min (bounds.Width - 3, MaxTabTextWidth));
  355. prevTab = tab;
  356. tab.Width = 2;
  357. tab.Height = Style.ShowTopLine ? 3 : 2;
  358. // if tab view is width <= 3 don't render any tabs
  359. if (maxWidth == 0)
  360. {
  361. tab.Visible = true;
  362. tab.MouseClick += Tab_MouseClick;
  363. yield return new TabToRender (i, tab, string.Empty, Equals (SelectedTab, tab), 0);
  364. break;
  365. }
  366. if (tabTextWidth > maxWidth)
  367. {
  368. text = tab.DisplayText.Substring (0, (int)maxWidth);
  369. tabTextWidth = (int)maxWidth;
  370. }
  371. tab.Width = Math.Max (tabTextWidth + 2, 1);
  372. tab.Height = Style.ShowTopLine ? 3 : 2;
  373. // if there is not enough space for this tab
  374. if (i + tabTextWidth >= bounds.Width)
  375. {
  376. tab.Visible = false;
  377. break;
  378. }
  379. // there is enough space!
  380. tab.Visible = true;
  381. tab.MouseClick += Tab_MouseClick;
  382. yield return new TabToRender (i, tab, text, Equals (SelectedTab, tab), tabTextWidth);
  383. i += tabTextWidth + 1;
  384. }
  385. }
  386. /// <summary>
  387. /// Returns the number of rows occupied by rendering the tabs, this depends on <see cref="TabStyle.ShowTopLine"/>
  388. /// and can be 0 (e.g. if <see cref="TabStyle.TabsOnBottom"/> and you ask for <paramref name="top"/>).
  389. /// </summary>
  390. /// <param name="top">True to measure the space required at the top of the control, false to measure space at the bottom.</param>
  391. /// .
  392. /// <returns></returns>
  393. private int GetTabHeight (bool top)
  394. {
  395. if (top && Style.TabsOnBottom)
  396. {
  397. return 0;
  398. }
  399. if (!top && !Style.TabsOnBottom)
  400. {
  401. return 0;
  402. }
  403. return Style.ShowTopLine ? 3 : 2;
  404. }
  405. private void Tab_MouseClick (object sender, MouseEventArgs e)
  406. {
  407. e.Handled = _tabsBar.NewMouseEvent (e) == true;
  408. }
  409. private void UnSetCurrentTabs ()
  410. {
  411. if (_tabLocations is { })
  412. {
  413. foreach (TabToRender tabToRender in _tabLocations)
  414. {
  415. tabToRender.Tab.MouseClick -= Tab_MouseClick;
  416. tabToRender.Tab.Visible = false;
  417. }
  418. _tabLocations = null;
  419. }
  420. }
  421. /// <summary>Raises the <see cref="TabClicked"/> event.</summary>
  422. /// <param name="tabMouseEventArgs"></param>
  423. private protected virtual void OnTabClicked (TabMouseEventArgs tabMouseEventArgs) { TabClicked?.Invoke (this, tabMouseEventArgs); }
  424. private class TabRowView : View
  425. {
  426. private readonly TabView _host;
  427. private readonly View _leftScrollIndicator;
  428. private readonly View _rightScrollIndicator;
  429. public TabRowView (TabView host)
  430. {
  431. _host = host;
  432. Id = "tabRowView";
  433. CanFocus = true;
  434. Height = 1; // BUGBUG: Views should avoid setting Height as doing so implies Frame.Size == GetContentSize ().
  435. Width = Dim.Fill ();
  436. _rightScrollIndicator = new View
  437. {
  438. Id = "rightScrollIndicator",
  439. Width = 1,
  440. Height = 1,
  441. Visible = false,
  442. Text = Glyphs.RightArrow.ToString ()
  443. };
  444. _rightScrollIndicator.MouseClick += _host.Tab_MouseClick;
  445. _leftScrollIndicator = new View
  446. {
  447. Id = "leftScrollIndicator",
  448. Width = 1,
  449. Height = 1,
  450. Visible = false,
  451. Text = Glyphs.LeftArrow.ToString ()
  452. };
  453. _leftScrollIndicator.MouseClick += _host.Tab_MouseClick;
  454. Add (_rightScrollIndicator, _leftScrollIndicator);
  455. }
  456. protected override bool OnMouseEvent (MouseEventArgs me)
  457. {
  458. Tab hit = me.View is Tab ? (Tab)me.View : null;
  459. if (me.IsSingleClicked)
  460. {
  461. _host.OnTabClicked (new TabMouseEventArgs (hit, me));
  462. // user canceled click
  463. if (me.Handled)
  464. {
  465. return true;
  466. }
  467. }
  468. if (!me.IsSingleDoubleOrTripleClicked)
  469. {
  470. return false;
  471. }
  472. if (!HasFocus && CanFocus)
  473. {
  474. SetFocus ();
  475. }
  476. if (me.IsSingleDoubleOrTripleClicked)
  477. {
  478. var scrollIndicatorHit = 0;
  479. if (me.View is { } && me.View.Id == "rightScrollIndicator")
  480. {
  481. scrollIndicatorHit = 1;
  482. }
  483. else if (me.View is { } && me.View.Id == "leftScrollIndicator")
  484. {
  485. scrollIndicatorHit = -1;
  486. }
  487. if (scrollIndicatorHit != 0)
  488. {
  489. _host.SwitchTabBy (scrollIndicatorHit);
  490. SetNeedsDisplay ();
  491. return true;
  492. }
  493. if (hit is { })
  494. {
  495. _host.SelectedTab = hit;
  496. SetNeedsDisplay ();
  497. return true;
  498. }
  499. }
  500. return false;
  501. }
  502. public override void OnDrawContent (Rectangle viewport)
  503. {
  504. _host._tabLocations = _host.CalculateViewport (Viewport).ToArray ();
  505. // clear any old text
  506. Clear ();
  507. RenderTabLine ();
  508. RenderUnderline ();
  509. Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ());
  510. }
  511. public override void OnDrawContentComplete (Rectangle viewport)
  512. {
  513. if (_host._tabLocations is null)
  514. {
  515. return;
  516. }
  517. TabToRender [] tabLocations = _host._tabLocations;
  518. int selectedTab = -1;
  519. for (var i = 0; i < tabLocations.Length; i++)
  520. {
  521. View tab = tabLocations [i].Tab;
  522. Rectangle vts = tab.ViewportToScreen (tab.Viewport);
  523. var lc = new LineCanvas ();
  524. int selectedOffset = _host.Style.ShowTopLine && tabLocations [i].IsSelected ? 0 : 1;
  525. if (tabLocations [i].IsSelected)
  526. {
  527. selectedTab = i;
  528. if (i == 0 && _host.TabScrollOffset == 0)
  529. {
  530. if (_host.Style.TabsOnBottom)
  531. {
  532. // Upper left vertical line
  533. lc.AddLine (
  534. new Point (vts.X - 1, vts.Y - 1),
  535. -1,
  536. Orientation.Vertical,
  537. tab.BorderStyle
  538. );
  539. }
  540. else
  541. {
  542. // Lower left vertical line
  543. lc.AddLine (
  544. new Point (vts.X - 1, vts.Bottom - selectedOffset),
  545. -1,
  546. Orientation.Vertical,
  547. tab.BorderStyle
  548. );
  549. }
  550. }
  551. else if (i > 0 && i <= tabLocations.Length - 1)
  552. {
  553. if (_host.Style.TabsOnBottom)
  554. {
  555. // URCorner
  556. lc.AddLine (
  557. new Point (vts.X - 1, vts.Y - 1),
  558. 1,
  559. Orientation.Vertical,
  560. tab.BorderStyle
  561. );
  562. lc.AddLine (
  563. new Point (vts.X - 1, vts.Y - 1),
  564. -1,
  565. Orientation.Horizontal,
  566. tab.BorderStyle
  567. );
  568. }
  569. else
  570. {
  571. // LRCorner
  572. lc.AddLine (
  573. new Point (vts.X - 1, vts.Bottom - selectedOffset),
  574. -1,
  575. Orientation.Vertical,
  576. tab.BorderStyle
  577. );
  578. lc.AddLine (
  579. new Point (vts.X - 1, vts.Bottom - selectedOffset),
  580. -1,
  581. Orientation.Horizontal,
  582. tab.BorderStyle
  583. );
  584. }
  585. if (_host.Style.ShowTopLine)
  586. {
  587. if (_host.Style.TabsOnBottom)
  588. {
  589. // Lower left tee
  590. lc.AddLine (
  591. new Point (vts.X - 1, vts.Bottom),
  592. -1,
  593. Orientation.Vertical,
  594. tab.BorderStyle
  595. );
  596. lc.AddLine (
  597. new Point (vts.X - 1, vts.Bottom),
  598. 0,
  599. Orientation.Horizontal,
  600. tab.BorderStyle
  601. );
  602. }
  603. else
  604. {
  605. // Upper left tee
  606. lc.AddLine (
  607. new Point (vts.X - 1, vts.Y - 1),
  608. 1,
  609. Orientation.Vertical,
  610. tab.BorderStyle
  611. );
  612. lc.AddLine (
  613. new Point (vts.X - 1, vts.Y - 1),
  614. 0,
  615. Orientation.Horizontal,
  616. tab.BorderStyle
  617. );
  618. }
  619. }
  620. }
  621. if (i < tabLocations.Length - 1)
  622. {
  623. if (_host.Style.ShowTopLine)
  624. {
  625. if (_host.Style.TabsOnBottom)
  626. {
  627. // Lower right tee
  628. lc.AddLine (
  629. new Point (vts.Right, vts.Bottom),
  630. -1,
  631. Orientation.Vertical,
  632. tab.BorderStyle
  633. );
  634. lc.AddLine (
  635. new Point (vts.Right, vts.Bottom),
  636. 0,
  637. Orientation.Horizontal,
  638. tab.BorderStyle
  639. );
  640. }
  641. else
  642. {
  643. // Upper right tee
  644. lc.AddLine (
  645. new Point (vts.Right, vts.Y - 1),
  646. 1,
  647. Orientation.Vertical,
  648. tab.BorderStyle
  649. );
  650. lc.AddLine (
  651. new Point (vts.Right, vts.Y - 1),
  652. 0,
  653. Orientation.Horizontal,
  654. tab.BorderStyle
  655. );
  656. }
  657. }
  658. }
  659. if (_host.Style.TabsOnBottom)
  660. {
  661. //URCorner
  662. lc.AddLine (
  663. new Point (vts.Right, vts.Y - 1),
  664. 1,
  665. Orientation.Vertical,
  666. tab.BorderStyle
  667. );
  668. lc.AddLine (
  669. new Point (vts.Right, vts.Y - 1),
  670. 1,
  671. Orientation.Horizontal,
  672. tab.BorderStyle
  673. );
  674. }
  675. else
  676. {
  677. //LLCorner
  678. lc.AddLine (
  679. new Point (vts.Right, vts.Bottom - selectedOffset),
  680. -1,
  681. Orientation.Vertical,
  682. tab.BorderStyle
  683. );
  684. lc.AddLine (
  685. new Point (vts.Right, vts.Bottom - selectedOffset),
  686. 1,
  687. Orientation.Horizontal,
  688. tab.BorderStyle
  689. );
  690. }
  691. }
  692. else if (selectedTab == -1)
  693. {
  694. if (i == 0 && string.IsNullOrEmpty (tab.Text))
  695. {
  696. if (_host.Style.TabsOnBottom)
  697. {
  698. if (_host.Style.ShowTopLine)
  699. {
  700. // LLCorner
  701. lc.AddLine (
  702. new Point (vts.X - 1, vts.Bottom),
  703. -1,
  704. Orientation.Vertical,
  705. tab.BorderStyle
  706. );
  707. lc.AddLine (
  708. new Point (vts.X - 1, vts.Bottom),
  709. 1,
  710. Orientation.Horizontal,
  711. tab.BorderStyle
  712. );
  713. }
  714. // ULCorner
  715. lc.AddLine (
  716. new Point (vts.X - 1, vts.Y - 1),
  717. 1,
  718. Orientation.Vertical,
  719. tab.BorderStyle
  720. );
  721. lc.AddLine (
  722. new Point (vts.X - 1, vts.Y - 1),
  723. 1,
  724. Orientation.Horizontal,
  725. tab.BorderStyle
  726. );
  727. }
  728. else
  729. {
  730. if (_host.Style.ShowTopLine)
  731. {
  732. // ULCorner
  733. lc.AddLine (
  734. new Point (vts.X - 1, vts.Y - 1),
  735. 1,
  736. Orientation.Vertical,
  737. tab.BorderStyle
  738. );
  739. lc.AddLine (
  740. new Point (vts.X - 1, vts.Y - 1),
  741. 1,
  742. Orientation.Horizontal,
  743. tab.BorderStyle
  744. );
  745. }
  746. // LLCorner
  747. lc.AddLine (
  748. new Point (vts.X - 1, vts.Bottom),
  749. -1,
  750. Orientation.Vertical,
  751. tab.BorderStyle
  752. );
  753. lc.AddLine (
  754. new Point (vts.X - 1, vts.Bottom),
  755. 1,
  756. Orientation.Horizontal,
  757. tab.BorderStyle
  758. );
  759. }
  760. }
  761. else if (i > 0)
  762. {
  763. if (_host.Style.ShowTopLine || _host.Style.TabsOnBottom)
  764. {
  765. // Upper left tee
  766. lc.AddLine (
  767. new Point (vts.X - 1, vts.Y - 1),
  768. 1,
  769. Orientation.Vertical,
  770. tab.BorderStyle
  771. );
  772. lc.AddLine (
  773. new Point (vts.X - 1, vts.Y - 1),
  774. 0,
  775. Orientation.Horizontal,
  776. tab.BorderStyle
  777. );
  778. }
  779. // Lower left tee
  780. lc.AddLine (
  781. new Point (vts.X - 1, vts.Bottom),
  782. -1,
  783. Orientation.Vertical,
  784. tab.BorderStyle
  785. );
  786. lc.AddLine (
  787. new Point (vts.X - 1, vts.Bottom),
  788. 0,
  789. Orientation.Horizontal,
  790. tab.BorderStyle
  791. );
  792. }
  793. }
  794. else if (i < tabLocations.Length - 1)
  795. {
  796. if (_host.Style.ShowTopLine)
  797. {
  798. // Upper right tee
  799. lc.AddLine (
  800. new Point (vts.Right, vts.Y - 1),
  801. 1,
  802. Orientation.Vertical,
  803. tab.BorderStyle
  804. );
  805. lc.AddLine (
  806. new Point (vts.Right, vts.Y - 1),
  807. 0,
  808. Orientation.Horizontal,
  809. tab.BorderStyle
  810. );
  811. }
  812. if (_host.Style.ShowTopLine || !_host.Style.TabsOnBottom)
  813. {
  814. // Lower right tee
  815. lc.AddLine (
  816. new Point (vts.Right, vts.Bottom),
  817. -1,
  818. Orientation.Vertical,
  819. tab.BorderStyle
  820. );
  821. lc.AddLine (
  822. new Point (vts.Right, vts.Bottom),
  823. 0,
  824. Orientation.Horizontal,
  825. tab.BorderStyle
  826. );
  827. }
  828. else
  829. {
  830. // Upper right tee
  831. lc.AddLine (
  832. new Point (vts.Right, vts.Y - 1),
  833. 1,
  834. Orientation.Vertical,
  835. tab.BorderStyle
  836. );
  837. lc.AddLine (
  838. new Point (vts.Right, vts.Y - 1),
  839. 0,
  840. Orientation.Horizontal,
  841. tab.BorderStyle
  842. );
  843. }
  844. }
  845. if (i == 0 && i != selectedTab && _host.TabScrollOffset == 0 && _host.Style.ShowBorder)
  846. {
  847. if (_host.Style.TabsOnBottom)
  848. {
  849. // Upper left vertical line
  850. lc.AddLine (
  851. new Point (vts.X - 1, vts.Y - 1),
  852. 0,
  853. Orientation.Vertical,
  854. tab.BorderStyle
  855. );
  856. lc.AddLine (
  857. new Point (vts.X - 1, vts.Y - 1),
  858. 1,
  859. Orientation.Horizontal,
  860. tab.BorderStyle
  861. );
  862. }
  863. else
  864. {
  865. // Lower left vertical line
  866. lc.AddLine (
  867. new Point (vts.X - 1, vts.Bottom),
  868. 0,
  869. Orientation.Vertical,
  870. tab.BorderStyle
  871. );
  872. lc.AddLine (
  873. new Point (vts.X - 1, vts.Bottom),
  874. 1,
  875. Orientation.Horizontal,
  876. tab.BorderStyle
  877. );
  878. }
  879. }
  880. if (i == tabLocations.Length - 1 && i != selectedTab)
  881. {
  882. if (_host.Style.TabsOnBottom)
  883. {
  884. // Upper right tee
  885. lc.AddLine (
  886. new Point (vts.Right, vts.Y - 1),
  887. 1,
  888. Orientation.Vertical,
  889. tab.BorderStyle
  890. );
  891. lc.AddLine (
  892. new Point (vts.Right, vts.Y - 1),
  893. 0,
  894. Orientation.Horizontal,
  895. tab.BorderStyle
  896. );
  897. }
  898. else
  899. {
  900. // Lower right tee
  901. lc.AddLine (
  902. new Point (vts.Right, vts.Bottom),
  903. -1,
  904. Orientation.Vertical,
  905. tab.BorderStyle
  906. );
  907. lc.AddLine (
  908. new Point (vts.Right, vts.Bottom),
  909. 0,
  910. Orientation.Horizontal,
  911. tab.BorderStyle
  912. );
  913. }
  914. }
  915. if (i == tabLocations.Length - 1)
  916. {
  917. var arrowOffset = 1;
  918. int lastSelectedTab = !_host.Style.ShowTopLine && i == selectedTab ? 1 :
  919. _host.Style.TabsOnBottom ? 1 : 0;
  920. Rectangle tabsBarVts = ViewportToScreen (Viewport);
  921. int lineLength = tabsBarVts.Right - vts.Right;
  922. // Right horizontal line
  923. if (ShouldDrawRightScrollIndicator ())
  924. {
  925. if (lineLength - arrowOffset > 0)
  926. {
  927. if (_host.Style.TabsOnBottom)
  928. {
  929. lc.AddLine (
  930. new Point (vts.Right, vts.Y - lastSelectedTab),
  931. lineLength - arrowOffset,
  932. Orientation.Horizontal,
  933. tab.BorderStyle
  934. );
  935. }
  936. else
  937. {
  938. lc.AddLine (
  939. new Point (
  940. vts.Right,
  941. vts.Bottom - lastSelectedTab
  942. ),
  943. lineLength - arrowOffset,
  944. Orientation.Horizontal,
  945. tab.BorderStyle
  946. );
  947. }
  948. }
  949. }
  950. else
  951. {
  952. if (_host.Style.TabsOnBottom)
  953. {
  954. lc.AddLine (
  955. new Point (vts.Right, vts.Y - lastSelectedTab),
  956. lineLength,
  957. Orientation.Horizontal,
  958. tab.BorderStyle
  959. );
  960. }
  961. else
  962. {
  963. lc.AddLine (
  964. new Point (vts.Right, vts.Bottom - lastSelectedTab),
  965. lineLength,
  966. Orientation.Horizontal,
  967. tab.BorderStyle
  968. );
  969. }
  970. if (_host.Style.ShowBorder)
  971. {
  972. if (_host.Style.TabsOnBottom)
  973. {
  974. // More LRCorner
  975. lc.AddLine (
  976. new Point (
  977. tabsBarVts.Right - 1,
  978. vts.Y - lastSelectedTab
  979. ),
  980. -1,
  981. Orientation.Vertical,
  982. tab.BorderStyle
  983. );
  984. }
  985. else
  986. {
  987. // More URCorner
  988. lc.AddLine (
  989. new Point (
  990. tabsBarVts.Right - 1,
  991. vts.Bottom - lastSelectedTab
  992. ),
  993. 1,
  994. Orientation.Vertical,
  995. tab.BorderStyle
  996. );
  997. }
  998. }
  999. }
  1000. }
  1001. tab.LineCanvas.Merge (lc);
  1002. tab.OnRenderLineCanvas ();
  1003. }
  1004. }
  1005. private int GetUnderlineYPosition ()
  1006. {
  1007. if (_host.Style.TabsOnBottom)
  1008. {
  1009. return 0;
  1010. }
  1011. return _host.Style.ShowTopLine ? 2 : 1;
  1012. }
  1013. /// <summary>Renders the line with the tab names in it.</summary>
  1014. private void RenderTabLine ()
  1015. {
  1016. TabToRender [] tabLocations = _host._tabLocations;
  1017. int y;
  1018. if (_host.Style.TabsOnBottom)
  1019. {
  1020. y = 1;
  1021. }
  1022. else
  1023. {
  1024. y = _host.Style.ShowTopLine ? 1 : 0;
  1025. }
  1026. View selected = null;
  1027. int topLine = _host.Style.ShowTopLine ? 1 : 0;
  1028. int width = Viewport.Width;
  1029. foreach (TabToRender toRender in tabLocations)
  1030. {
  1031. Tab tab = toRender.Tab;
  1032. if (toRender.IsSelected)
  1033. {
  1034. selected = tab;
  1035. if (_host.Style.TabsOnBottom)
  1036. {
  1037. tab.Border.Thickness = new Thickness (1, 0, 1, topLine);
  1038. tab.Margin.Thickness = new Thickness (0, 1, 0, 0);
  1039. }
  1040. else
  1041. {
  1042. tab.Border.Thickness = new Thickness (1, topLine, 1, 0);
  1043. tab.Margin.Thickness = new Thickness (0, 0, 0, topLine);
  1044. }
  1045. }
  1046. else if (selected is null)
  1047. {
  1048. if (_host.Style.TabsOnBottom)
  1049. {
  1050. tab.Border.Thickness = new Thickness (1, 1, 0, topLine);
  1051. tab.Margin.Thickness = new Thickness (0, 0, 0, 0);
  1052. }
  1053. else
  1054. {
  1055. tab.Border.Thickness = new Thickness (1, topLine, 0, 1);
  1056. tab.Margin.Thickness = new Thickness (0, 0, 0, 0);
  1057. }
  1058. tab.Width = Math.Max (tab.Width.GetAnchor (0) - 1, 1);
  1059. }
  1060. else
  1061. {
  1062. if (_host.Style.TabsOnBottom)
  1063. {
  1064. tab.Border.Thickness = new Thickness (0, 1, 1, topLine);
  1065. tab.Margin.Thickness = new Thickness (0, 0, 0, 0);
  1066. }
  1067. else
  1068. {
  1069. tab.Border.Thickness = new Thickness (0, topLine, 1, 1);
  1070. tab.Margin.Thickness = new Thickness (0, 0, 0, 0);
  1071. }
  1072. tab.Width = Math.Max (tab.Width.GetAnchor (0) - 1, 1);
  1073. }
  1074. tab.Text = toRender.TextToRender;
  1075. LayoutSubviews ();
  1076. tab.OnDrawAdornments ();
  1077. Attribute prevAttr = Driver.GetAttribute ();
  1078. // if tab is the selected one and focus is inside this control
  1079. if (toRender.IsSelected && _host.HasFocus)
  1080. {
  1081. if (_host.Focused == this)
  1082. {
  1083. // if focus is the tab bar itself then show that they can switch tabs
  1084. prevAttr = ColorScheme.HotFocus;
  1085. }
  1086. else
  1087. {
  1088. // Focus is inside the tab
  1089. prevAttr = ColorScheme.HotNormal;
  1090. }
  1091. }
  1092. tab.TextFormatter.Draw (
  1093. tab.ViewportToScreen (tab.Viewport),
  1094. prevAttr,
  1095. ColorScheme.HotNormal
  1096. );
  1097. tab.OnRenderLineCanvas ();
  1098. Driver.SetAttribute (GetNormalColor ());
  1099. }
  1100. }
  1101. /// <summary>Renders the line of the tab that adjoins the content of the tab.</summary>
  1102. private void RenderUnderline ()
  1103. {
  1104. int y = GetUnderlineYPosition ();
  1105. TabToRender selected = _host._tabLocations.FirstOrDefault (t => t.IsSelected);
  1106. if (selected is null)
  1107. {
  1108. return;
  1109. }
  1110. // draw scroll indicators
  1111. // if there are more tabs to the left not visible
  1112. if (_host.TabScrollOffset > 0)
  1113. {
  1114. _leftScrollIndicator.X = 0;
  1115. _leftScrollIndicator.Y = y;
  1116. // indicate that
  1117. _leftScrollIndicator.Visible = true;
  1118. // Ensures this is clicked instead of the first tab
  1119. MoveSubviewToEnd (_leftScrollIndicator);
  1120. _leftScrollIndicator.Draw ();
  1121. }
  1122. else
  1123. {
  1124. _leftScrollIndicator.Visible = false;
  1125. }
  1126. // if there are more tabs to the right not visible
  1127. if (ShouldDrawRightScrollIndicator ())
  1128. {
  1129. _rightScrollIndicator.X = Viewport.Width - 1;
  1130. _rightScrollIndicator.Y = y;
  1131. // indicate that
  1132. _rightScrollIndicator.Visible = true;
  1133. // Ensures this is clicked instead of the last tab if under this
  1134. MoveSubviewToStart (_rightScrollIndicator);
  1135. _rightScrollIndicator.Draw ();
  1136. }
  1137. else
  1138. {
  1139. _rightScrollIndicator.Visible = false;
  1140. }
  1141. }
  1142. private bool ShouldDrawRightScrollIndicator () { return _host._tabLocations.LastOrDefault ()?.Tab != _host.Tabs.LastOrDefault (); }
  1143. }
  1144. private class TabToRender
  1145. {
  1146. public TabToRender (int x, Tab tab, string textToRender, bool isSelected, int width)
  1147. {
  1148. X = x;
  1149. Tab = tab;
  1150. IsSelected = isSelected;
  1151. Width = width;
  1152. TextToRender = textToRender;
  1153. }
  1154. /// <summary>True if the tab that is being rendered is the selected one.</summary>
  1155. /// <value></value>
  1156. public bool IsSelected { get; }
  1157. public Tab Tab { get; }
  1158. public string TextToRender { get; }
  1159. public int Width { get; }
  1160. public int X { get; set; }
  1161. }
  1162. }