TabRow.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800
  1. namespace Terminal.Gui.Views;
  2. internal class TabRow : View
  3. {
  4. private readonly TabView _host;
  5. private readonly View _leftScrollIndicator;
  6. private readonly View _rightScrollIndicator;
  7. public TabRow (TabView host)
  8. {
  9. _host = host;
  10. Id = "tabRow";
  11. CanFocus = true;
  12. TabStop = TabBehavior.TabGroup;
  13. Width = Dim.Fill ();
  14. _rightScrollIndicator = new View
  15. {
  16. Id = "rightScrollIndicator",
  17. Width = 1,
  18. Height = 1,
  19. Visible = false,
  20. Text = Glyphs.RightArrow.ToString ()
  21. };
  22. _rightScrollIndicator.Selecting += (s, e) =>
  23. {
  24. _host.Tab_Selecting (s, e);
  25. };
  26. _leftScrollIndicator = new View
  27. {
  28. Id = "leftScrollIndicator",
  29. Width = 1,
  30. Height = 1,
  31. Visible = false,
  32. Text = Glyphs.LeftArrow.ToString ()
  33. };
  34. _leftScrollIndicator.Selecting += (s, e) =>
  35. {
  36. _host.Tab_Selecting (s, e);
  37. };
  38. Add (_rightScrollIndicator, _leftScrollIndicator);
  39. }
  40. protected override bool OnMouseEvent (MouseEventArgs me)
  41. {
  42. View? parent = me.View is Adornment adornment ? adornment.Parent : me.View;
  43. Tab? hit = parent as Tab;
  44. if (me.IsSingleClicked)
  45. {
  46. _host.OnTabClicked (new TabMouseEventArgs (hit!, me));
  47. // user canceled click
  48. if (me.Handled)
  49. {
  50. return true;
  51. }
  52. if (parent == _host.SelectedTab)
  53. {
  54. _host.SelectedTab?.SetFocus ();
  55. }
  56. }
  57. if (me.IsWheel && !HasFocus && CanFocus)
  58. {
  59. SetFocus ();
  60. }
  61. if (me is { IsSingleDoubleOrTripleClicked: false, IsWheel: false })
  62. {
  63. return false;
  64. }
  65. if (me.IsSingleDoubleOrTripleClicked || me.IsWheel)
  66. {
  67. var scrollIndicatorHit = 0;
  68. if (me.View is { Id: "rightScrollIndicator" } || me.Flags.HasFlag (MouseFlags.WheeledDown) || me.Flags.HasFlag (MouseFlags.WheeledRight))
  69. {
  70. scrollIndicatorHit = 1;
  71. }
  72. else if (me.View is { Id: "leftScrollIndicator" } || me.Flags.HasFlag (MouseFlags.WheeledUp) || me.Flags.HasFlag (MouseFlags.WheeledLeft))
  73. {
  74. scrollIndicatorHit = -1;
  75. }
  76. if (scrollIndicatorHit != 0)
  77. {
  78. _host.SwitchTabBy (scrollIndicatorHit);
  79. return true;
  80. }
  81. if (hit is { })
  82. {
  83. _host.SelectedTab = hit;
  84. return true;
  85. }
  86. }
  87. return false;
  88. }
  89. /// <inheritdoc />
  90. protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedView)
  91. {
  92. if (_host.SelectedTab is { HasFocus: false, CanFocus: true } && focusedView == this)
  93. {
  94. _host.SelectedTab?.SetFocus ();
  95. return;
  96. }
  97. base.OnHasFocusChanged (newHasFocus, previousFocusedView, focusedView);
  98. }
  99. /// <inheritdoc/>
  100. protected override void OnSubViewLayout (LayoutEventArgs args)
  101. {
  102. _host._tabLocations = _host.CalculateViewport (Viewport).ToArray ();
  103. RenderTabLine ();
  104. RenderUnderline ();
  105. base.OnSubViewLayout (args);
  106. }
  107. /// <inheritdoc />
  108. protected override bool OnRenderingLineCanvas ()
  109. {
  110. RenderTabLineCanvas ();
  111. return false;
  112. }
  113. private void RenderTabLineCanvas ()
  114. {
  115. if (_host._tabLocations is null)
  116. {
  117. return;
  118. }
  119. Tab [] tabLocations = _host._tabLocations;
  120. int selectedTab = -1;
  121. var lc = new LineCanvas ();
  122. for (var i = 0; i < tabLocations.Length; i++)
  123. {
  124. View tab = tabLocations [i];
  125. Rectangle vts = tab.ViewportToScreen (tab.Viewport);
  126. int selectedOffset = _host.Style.ShowTopLine && tabLocations [i] == _host.SelectedTab ? 0 : 1;
  127. if (tabLocations [i] == _host.SelectedTab)
  128. {
  129. selectedTab = i;
  130. if (i == 0 && _host.TabScrollOffset == 0)
  131. {
  132. if (_host.Style.TabsOnBottom)
  133. {
  134. // Upper left vertical line
  135. lc.AddLine (
  136. new Point (vts.X - 1, vts.Y - 1),
  137. -1,
  138. Orientation.Vertical,
  139. tab.BorderStyle
  140. );
  141. }
  142. else
  143. {
  144. // Lower left vertical line
  145. lc.AddLine (
  146. new Point (vts.X - 1, vts.Bottom - selectedOffset),
  147. -1,
  148. Orientation.Vertical,
  149. tab.BorderStyle
  150. );
  151. }
  152. }
  153. else if (i > 0 && i <= tabLocations.Length - 1)
  154. {
  155. if (_host.Style.TabsOnBottom)
  156. {
  157. // URCorner
  158. lc.AddLine (
  159. new Point (vts.X - 1, vts.Y - 1),
  160. 1,
  161. Orientation.Vertical,
  162. tab.BorderStyle
  163. );
  164. lc.AddLine (
  165. new Point (vts.X - 1, vts.Y - 1),
  166. -1,
  167. Orientation.Horizontal,
  168. tab.BorderStyle
  169. );
  170. }
  171. else
  172. {
  173. // LRCorner
  174. lc.AddLine (
  175. new Point (vts.X - 1, vts.Bottom - selectedOffset),
  176. -1,
  177. Orientation.Vertical,
  178. tab.BorderStyle
  179. );
  180. lc.AddLine (
  181. new Point (vts.X - 1, vts.Bottom - selectedOffset),
  182. -1,
  183. Orientation.Horizontal,
  184. tab.BorderStyle
  185. );
  186. }
  187. if (_host.Style.ShowTopLine)
  188. {
  189. if (_host.Style.TabsOnBottom)
  190. {
  191. // Lower left tee
  192. lc.AddLine (
  193. new Point (vts.X - 1, vts.Bottom),
  194. -1,
  195. Orientation.Vertical,
  196. tab.BorderStyle
  197. );
  198. lc.AddLine (
  199. new Point (vts.X - 1, vts.Bottom),
  200. 0,
  201. Orientation.Horizontal,
  202. tab.BorderStyle
  203. );
  204. }
  205. else
  206. {
  207. // Upper left tee
  208. lc.AddLine (
  209. new Point (vts.X - 1, vts.Y - 1),
  210. 1,
  211. Orientation.Vertical,
  212. tab.BorderStyle
  213. );
  214. lc.AddLine (
  215. new Point (vts.X - 1, vts.Y - 1),
  216. 0,
  217. Orientation.Horizontal,
  218. tab.BorderStyle
  219. );
  220. }
  221. }
  222. }
  223. if (i < tabLocations.Length - 1)
  224. {
  225. if (_host.Style.ShowTopLine)
  226. {
  227. if (_host.Style.TabsOnBottom)
  228. {
  229. // Lower right tee
  230. lc.AddLine (
  231. new Point (vts.Right, vts.Bottom),
  232. -1,
  233. Orientation.Vertical,
  234. tab.BorderStyle
  235. );
  236. lc.AddLine (
  237. new Point (vts.Right, vts.Bottom),
  238. 0,
  239. Orientation.Horizontal,
  240. tab.BorderStyle
  241. );
  242. }
  243. else
  244. {
  245. // Upper right tee
  246. lc.AddLine (
  247. new Point (vts.Right, vts.Y - 1),
  248. 1,
  249. Orientation.Vertical,
  250. tab.BorderStyle
  251. );
  252. lc.AddLine (
  253. new Point (vts.Right, vts.Y - 1),
  254. 0,
  255. Orientation.Horizontal,
  256. tab.BorderStyle
  257. );
  258. }
  259. }
  260. }
  261. if (_host.Style.TabsOnBottom)
  262. {
  263. //URCorner
  264. lc.AddLine (
  265. new Point (vts.Right, vts.Y - 1),
  266. 1,
  267. Orientation.Vertical,
  268. tab.BorderStyle
  269. );
  270. lc.AddLine (
  271. new Point (vts.Right, vts.Y - 1),
  272. 1,
  273. Orientation.Horizontal,
  274. tab.BorderStyle
  275. );
  276. }
  277. else
  278. {
  279. //LLCorner
  280. lc.AddLine (
  281. new Point (vts.Right, vts.Bottom - selectedOffset),
  282. -1,
  283. Orientation.Vertical,
  284. tab.BorderStyle
  285. );
  286. lc.AddLine (
  287. new Point (vts.Right, vts.Bottom - selectedOffset),
  288. 1,
  289. Orientation.Horizontal,
  290. tab.BorderStyle
  291. );
  292. }
  293. }
  294. else if (selectedTab == -1)
  295. {
  296. if (i == 0 && string.IsNullOrEmpty (tab.Text))
  297. {
  298. if (_host.Style.TabsOnBottom)
  299. {
  300. if (_host.Style.ShowTopLine)
  301. {
  302. // LLCorner
  303. lc.AddLine (
  304. new Point (vts.X - 1, vts.Bottom),
  305. -1,
  306. Orientation.Vertical,
  307. tab.BorderStyle
  308. );
  309. lc.AddLine (
  310. new Point (vts.X - 1, vts.Bottom),
  311. 1,
  312. Orientation.Horizontal,
  313. tab.BorderStyle
  314. );
  315. }
  316. // ULCorner
  317. lc.AddLine (
  318. new Point (vts.X - 1, vts.Y - 1),
  319. 1,
  320. Orientation.Vertical,
  321. tab.BorderStyle
  322. );
  323. lc.AddLine (
  324. new Point (vts.X - 1, vts.Y - 1),
  325. 1,
  326. Orientation.Horizontal,
  327. tab.BorderStyle
  328. );
  329. }
  330. else
  331. {
  332. if (_host.Style.ShowTopLine)
  333. {
  334. // ULCorner
  335. lc.AddLine (
  336. new Point (vts.X - 1, vts.Y - 1),
  337. 1,
  338. Orientation.Vertical,
  339. tab.BorderStyle
  340. );
  341. lc.AddLine (
  342. new Point (vts.X - 1, vts.Y - 1),
  343. 1,
  344. Orientation.Horizontal,
  345. tab.BorderStyle
  346. );
  347. }
  348. // LLCorner
  349. lc.AddLine (
  350. new Point (vts.X - 1, vts.Bottom),
  351. -1,
  352. Orientation.Vertical,
  353. tab.BorderStyle
  354. );
  355. lc.AddLine (
  356. new Point (vts.X - 1, vts.Bottom),
  357. 1,
  358. Orientation.Horizontal,
  359. tab.BorderStyle
  360. );
  361. }
  362. }
  363. else if (i > 0)
  364. {
  365. if (_host.Style.ShowTopLine || _host.Style.TabsOnBottom)
  366. {
  367. // Upper left tee
  368. lc.AddLine (
  369. new Point (vts.X - 1, vts.Y - 1),
  370. 1,
  371. Orientation.Vertical,
  372. tab.BorderStyle
  373. );
  374. lc.AddLine (
  375. new Point (vts.X - 1, vts.Y - 1),
  376. 0,
  377. Orientation.Horizontal,
  378. tab.BorderStyle
  379. );
  380. }
  381. // Lower left tee
  382. lc.AddLine (
  383. new Point (vts.X - 1, vts.Bottom),
  384. -1,
  385. Orientation.Vertical,
  386. tab.BorderStyle
  387. );
  388. lc.AddLine (
  389. new Point (vts.X - 1, vts.Bottom),
  390. 0,
  391. Orientation.Horizontal,
  392. tab.BorderStyle
  393. );
  394. }
  395. }
  396. else if (i < tabLocations.Length - 1)
  397. {
  398. if (_host.Style.ShowTopLine)
  399. {
  400. // Upper right tee
  401. lc.AddLine (
  402. new Point (vts.Right, vts.Y - 1),
  403. 1,
  404. Orientation.Vertical,
  405. tab.BorderStyle
  406. );
  407. lc.AddLine (
  408. new Point (vts.Right, vts.Y - 1),
  409. 0,
  410. Orientation.Horizontal,
  411. tab.BorderStyle
  412. );
  413. }
  414. if (_host.Style.ShowTopLine || !_host.Style.TabsOnBottom)
  415. {
  416. // Lower right tee
  417. lc.AddLine (
  418. new Point (vts.Right, vts.Bottom),
  419. -1,
  420. Orientation.Vertical,
  421. tab.BorderStyle
  422. );
  423. lc.AddLine (
  424. new Point (vts.Right, vts.Bottom),
  425. 0,
  426. Orientation.Horizontal,
  427. tab.BorderStyle
  428. );
  429. }
  430. else
  431. {
  432. // Upper right tee
  433. lc.AddLine (
  434. new Point (vts.Right, vts.Y - 1),
  435. 1,
  436. Orientation.Vertical,
  437. tab.BorderStyle
  438. );
  439. lc.AddLine (
  440. new Point (vts.Right, vts.Y - 1),
  441. 0,
  442. Orientation.Horizontal,
  443. tab.BorderStyle
  444. );
  445. }
  446. }
  447. if (i == 0 && i != selectedTab && _host is { TabScrollOffset: 0, Style.ShowBorder: true })
  448. {
  449. if (_host.Style.TabsOnBottom)
  450. {
  451. // Upper left vertical line
  452. lc.AddLine (
  453. new Point (vts.X - 1, vts.Y - 1),
  454. 0,
  455. Orientation.Vertical,
  456. tab.BorderStyle
  457. );
  458. lc.AddLine (
  459. new Point (vts.X - 1, vts.Y - 1),
  460. 1,
  461. Orientation.Horizontal,
  462. tab.BorderStyle
  463. );
  464. }
  465. else
  466. {
  467. // Lower left vertical line
  468. lc.AddLine (
  469. new Point (vts.X - 1, vts.Bottom),
  470. 0,
  471. Orientation.Vertical,
  472. tab.BorderStyle
  473. );
  474. lc.AddLine (
  475. new Point (vts.X - 1, vts.Bottom),
  476. 1,
  477. Orientation.Horizontal,
  478. tab.BorderStyle
  479. );
  480. }
  481. }
  482. if (i == tabLocations.Length - 1 && i != selectedTab)
  483. {
  484. if (_host.Style.TabsOnBottom)
  485. {
  486. // Upper right tee
  487. lc.AddLine (
  488. new Point (vts.Right, vts.Y - 1),
  489. 1,
  490. Orientation.Vertical,
  491. tab.BorderStyle
  492. );
  493. lc.AddLine (
  494. new Point (vts.Right, vts.Y - 1),
  495. 0,
  496. Orientation.Horizontal,
  497. tab.BorderStyle
  498. );
  499. }
  500. else
  501. {
  502. // Lower right tee
  503. lc.AddLine (
  504. new Point (vts.Right, vts.Bottom),
  505. -1,
  506. Orientation.Vertical,
  507. tab.BorderStyle
  508. );
  509. lc.AddLine (
  510. new Point (vts.Right, vts.Bottom),
  511. 0,
  512. Orientation.Horizontal,
  513. tab.BorderStyle
  514. );
  515. }
  516. }
  517. if (i == tabLocations.Length - 1)
  518. {
  519. var arrowOffset = 1;
  520. int lastSelectedTab = !_host.Style.ShowTopLine && i == selectedTab ? 1 :
  521. _host.Style.TabsOnBottom ? 1 : 0;
  522. Rectangle tabsBarVts = ViewportToScreen (Viewport);
  523. int lineLength = tabsBarVts.Right - vts.Right;
  524. // Right horizontal line
  525. if (ShouldDrawRightScrollIndicator ())
  526. {
  527. if (lineLength - arrowOffset > 0)
  528. {
  529. if (_host.Style.TabsOnBottom)
  530. {
  531. lc.AddLine (
  532. new Point (vts.Right, vts.Y - lastSelectedTab),
  533. lineLength - arrowOffset,
  534. Orientation.Horizontal,
  535. tab.BorderStyle
  536. );
  537. }
  538. else
  539. {
  540. lc.AddLine (
  541. new Point (
  542. vts.Right,
  543. vts.Bottom - lastSelectedTab
  544. ),
  545. lineLength - arrowOffset,
  546. Orientation.Horizontal,
  547. tab.BorderStyle
  548. );
  549. }
  550. }
  551. }
  552. else
  553. {
  554. // Right corner
  555. if (_host.Style.TabsOnBottom)
  556. {
  557. lc.AddLine (
  558. new Point (vts.Right, vts.Y - lastSelectedTab),
  559. lineLength,
  560. Orientation.Horizontal,
  561. tab.BorderStyle
  562. );
  563. }
  564. else
  565. {
  566. lc.AddLine (
  567. new Point (vts.Right, vts.Bottom - lastSelectedTab),
  568. lineLength,
  569. Orientation.Horizontal,
  570. tab.BorderStyle
  571. );
  572. }
  573. if (_host.Style.ShowBorder)
  574. {
  575. if (_host.Style.TabsOnBottom)
  576. {
  577. // More LRCorner
  578. lc.AddLine (
  579. new Point (
  580. tabsBarVts.Right - 1,
  581. vts.Y - lastSelectedTab
  582. ),
  583. -1,
  584. Orientation.Vertical,
  585. tab.BorderStyle
  586. );
  587. }
  588. else
  589. {
  590. // More URCorner
  591. lc.AddLine (
  592. new Point (
  593. tabsBarVts.Right - 1,
  594. vts.Bottom - lastSelectedTab
  595. ),
  596. 1,
  597. Orientation.Vertical,
  598. tab.BorderStyle
  599. );
  600. }
  601. }
  602. }
  603. }
  604. }
  605. _host.LineCanvas.Merge (lc);
  606. }
  607. private int GetUnderlineYPosition ()
  608. {
  609. if (_host.Style.TabsOnBottom)
  610. {
  611. return 0;
  612. }
  613. return _host.Style.ShowTopLine ? 2 : 1;
  614. }
  615. /// <summary>Renders the line with the tab names in it.</summary>
  616. private void RenderTabLine ()
  617. {
  618. if (_host._tabLocations is null)
  619. {
  620. return;
  621. }
  622. View? selected = null;
  623. int topLine = _host.Style.ShowTopLine ? 1 : 0;
  624. foreach (Tab toRender in _host._tabLocations)
  625. {
  626. Tab tab = toRender;
  627. if (toRender == _host.SelectedTab)
  628. {
  629. selected = tab;
  630. if (_host.Style.TabsOnBottom)
  631. {
  632. tab.Border!.Thickness = new (1, 0, 1, topLine);
  633. tab.Margin!.Thickness = new (0, 1, 0, 0);
  634. }
  635. else
  636. {
  637. tab.Border!.Thickness = new (1, topLine, 1, 0);
  638. tab.Margin!.Thickness = new (0, 0, 0, topLine);
  639. }
  640. }
  641. else if (selected is null)
  642. {
  643. if (_host.Style.TabsOnBottom)
  644. {
  645. tab.Border!.Thickness = new (1, 1, 1, topLine);
  646. tab.Margin!.Thickness = new (0, 0, 0, 0);
  647. }
  648. else
  649. {
  650. tab.Border!.Thickness = new (1, topLine, 1, 1);
  651. tab.Margin!.Thickness = new (0, 0, 0, 0);
  652. }
  653. }
  654. else
  655. {
  656. if (_host.Style.TabsOnBottom)
  657. {
  658. tab.Border!.Thickness = new (1, 1, 1, topLine);
  659. tab.Margin!.Thickness = new (0, 0, 0, 0);
  660. }
  661. else
  662. {
  663. tab.Border!.Thickness = new (1, topLine, 1, 1);
  664. tab.Margin!.Thickness = new (0, 0, 0, 0);
  665. }
  666. }
  667. // Ensures updating TextFormatter constrains
  668. tab.TextFormatter.ConstrainToWidth = tab.GetContentSize ().Width;
  669. tab.TextFormatter.ConstrainToHeight = tab.GetContentSize ().Height;
  670. }
  671. }
  672. /// <summary>Renders the line of the tab that adjoins the content of the tab.</summary>
  673. private void RenderUnderline ()
  674. {
  675. int y = GetUnderlineYPosition ();
  676. Tab? selected = _host._tabLocations?.FirstOrDefault (t => t == _host.SelectedTab);
  677. if (selected is null)
  678. {
  679. return;
  680. }
  681. // draw scroll indicators
  682. // if there are more tabs to the left not visible
  683. if (_host.TabScrollOffset > 0)
  684. {
  685. _leftScrollIndicator.X = 0;
  686. _leftScrollIndicator.Y = y;
  687. // indicate that
  688. _leftScrollIndicator.Visible = true;
  689. // Ensures this is clicked instead of the first tab
  690. MoveSubViewToEnd (_leftScrollIndicator);
  691. }
  692. else
  693. {
  694. _leftScrollIndicator.Visible = false;
  695. }
  696. // if there are more tabs to the right not visible
  697. if (ShouldDrawRightScrollIndicator ())
  698. {
  699. _rightScrollIndicator.X = Viewport.Width - 1;
  700. _rightScrollIndicator.Y = y;
  701. // indicate that
  702. _rightScrollIndicator.Visible = true;
  703. // Ensures this is clicked instead of the last tab if under this
  704. MoveSubViewToStart (_rightScrollIndicator);
  705. }
  706. else
  707. {
  708. _rightScrollIndicator.Visible = false;
  709. }
  710. }
  711. private bool ShouldDrawRightScrollIndicator () { return _host._tabLocations!.LastOrDefault () != _host.Tabs.LastOrDefault (); }
  712. }