TabRowView.cs 28 KB

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