TabRowView.cs 28 KB

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