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