TabRowView.cs 28 KB

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