TabRowView.cs 28 KB

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