Border.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. namespace Terminal.Gui;
  2. /// <summary>The Border for a <see cref="View"/>.</summary>
  3. /// <remarks>
  4. /// <para>
  5. /// Renders a border around the view with the <see cref="View.Title"/>. A border using <see cref="LineStyle"/>
  6. /// will be drawn on the sides of <see cref="Thickness"/> that are greater than zero.
  7. /// </para>
  8. /// <para>
  9. /// The <see cref="View.Title"/> of <see cref="Adornment.Parent"/> will be drawn based on the value of
  10. /// <see cref="Thickness.Top"/>:
  11. /// </para>
  12. /// <para>
  13. /// If <c>1</c>:
  14. /// <code>
  15. /// ┌┤1234├──┐
  16. /// │ │
  17. /// └────────┘
  18. /// </code>
  19. /// </para>
  20. /// <para>
  21. /// If <c>2</c>:
  22. /// <code>
  23. /// ┌────┐
  24. /// ┌┤1234├──┐
  25. /// │ │
  26. /// └────────┘
  27. /// </code>
  28. /// </para>
  29. /// <para>
  30. /// If <c>3</c>:
  31. /// <code>
  32. /// ┌────┐
  33. /// ┌┤1234├──┐
  34. /// │└────┘ │
  35. /// │ │
  36. /// └────────┘
  37. /// </code>
  38. /// </para>
  39. /// <para/>
  40. /// <para>See the <see cref="Adornment"/> class.</para>
  41. /// </remarks>
  42. public class Border : Adornment
  43. {
  44. private LineStyle? _lineStyle;
  45. /// <inheritdoc/>
  46. public Border ()
  47. { /* Do nothing; A parameter-less constructor is required to support all views unit tests. */
  48. }
  49. /// <summary>
  50. /// The close button for the border. Set to <see cref="Button.Visible"/>, to <see langword="true"/> to enable.
  51. /// </summary>
  52. public Button CloseButton { get; internal set; }
  53. /// <inheritdoc/>
  54. public Border (View parent) : base (parent)
  55. {
  56. /* Do nothing; View.CreateAdornment requires a constructor that takes a parent */
  57. Parent = parent;
  58. }
  59. /// <inheritdoc/>
  60. public override void BeginInit ()
  61. {
  62. base.BeginInit ();
  63. if (Parent is { })
  64. {
  65. CloseButton = new Button ()
  66. {
  67. Text = "X", // So it's not visible to not break unit tests
  68. Y = 0,
  69. CanFocus = true,
  70. NoDecorations = false,
  71. Visible = false,
  72. };
  73. //CloseButton.BorderStyle = LineStyle.Single;
  74. //CloseButton.Border.Thickness = new (1, 0, 1, 0);
  75. CloseButton.X = Pos.AnchorEnd () - (Pos.Right (CloseButton) - Pos.Left (CloseButton)) + Thickness.Left + 1; // +1 for the border
  76. Add (CloseButton);
  77. CloseButton.Accept += (s, e) => {
  78. e.Cancel = Parent.InvokeCommand (Command.QuitToplevel) == true;
  79. };
  80. }
  81. }
  82. /// <summary>
  83. /// The color scheme for the Border. If set to <see langword="null"/>, gets the <see cref="Adornment.Parent"/>
  84. /// scheme. color scheme.
  85. /// </summary>
  86. public override ColorScheme ColorScheme
  87. {
  88. get
  89. {
  90. if (base.ColorScheme is { })
  91. {
  92. return base.ColorScheme;
  93. }
  94. return Parent?.ColorScheme;
  95. }
  96. set
  97. {
  98. base.ColorScheme = value;
  99. Parent?.SetNeedsDisplay ();
  100. }
  101. }
  102. /// <summary>
  103. /// Sets the style of the border by changing the <see cref="Thickness"/>. This is a helper API for setting the
  104. /// <see cref="Thickness"/> to <c>(1,1,1,1)</c> and setting the line style of the views that comprise the border. If
  105. /// set to <see cref="LineStyle.None"/> no border will be drawn.
  106. /// </summary>
  107. public LineStyle LineStyle
  108. {
  109. get
  110. {
  111. if (_lineStyle.HasValue)
  112. {
  113. return _lineStyle.Value;
  114. }
  115. // TODO: Make Border.LineStyle inherit from the SuperView hierarchy
  116. // TODO: Right now, Window and FrameView use CM to set BorderStyle, which negates
  117. // TODO: all this.
  118. return Parent.SuperView?.BorderStyle ?? LineStyle.None;
  119. }
  120. set => _lineStyle = value;
  121. }
  122. /// <inheritdoc/>
  123. public override void OnDrawContent (Rectangle contentArea)
  124. {
  125. base.OnDrawContent (contentArea);
  126. if (Thickness == Thickness.Empty)
  127. {
  128. return;
  129. }
  130. //Driver.SetAttribute (Colors.ColorSchemes ["Error"].Normal);
  131. Rectangle screenBounds = BoundsToScreen (contentArea);
  132. //OnDrawSubviews (bounds);
  133. // TODO: v2 - this will eventually be two controls: "BorderView" and "Label" (for the title)
  134. // The border adornment (and title) are drawn at the outermost edge of border;
  135. // For Border
  136. // ...thickness extends outward (border/title is always as far in as possible)
  137. // PERF: How about a call to Rectangle.Offset?
  138. Rectangle borderBounds = new (
  139. screenBounds.X + Math.Max (0, Thickness.Left - 1),
  140. screenBounds.Y + Math.Max (0, Thickness.Top - 1),
  141. Math.Max (
  142. 0,
  143. screenBounds.Width
  144. - Math.Max (
  145. 0,
  146. Math.Max (0, Thickness.Left - 1)
  147. + Math.Max (0, Thickness.Right - 1)
  148. )
  149. ),
  150. Math.Max (
  151. 0,
  152. screenBounds.Height
  153. - Math.Max (
  154. 0,
  155. Math.Max (0, Thickness.Top - 1)
  156. + Math.Max (0, Thickness.Bottom - 1)
  157. )
  158. )
  159. );
  160. int topTitleLineY = borderBounds.Y;
  161. int titleY = borderBounds.Y;
  162. var titleBarsLength = 0; // the little vertical thingies
  163. int maxTitleWidth = Math.Max (
  164. 0,
  165. Math.Min (
  166. Parent.TitleTextFormatter.FormatAndGetSize ().Width,
  167. Math.Min (screenBounds.Width - 4, borderBounds.Width - 4)
  168. )
  169. );
  170. Parent.TitleTextFormatter.Size = new (maxTitleWidth, 1);
  171. int sideLineLength = borderBounds.Height;
  172. bool canDrawBorder = borderBounds is { Width: > 0, Height: > 0 };
  173. if (!string.IsNullOrEmpty (Parent?.Title))
  174. {
  175. if (Thickness.Top == 2)
  176. {
  177. topTitleLineY = borderBounds.Y - 1;
  178. titleY = topTitleLineY + 1;
  179. titleBarsLength = 2;
  180. }
  181. // ┌────┐
  182. //┌┘View└
  183. //│
  184. if (Thickness.Top == 3)
  185. {
  186. topTitleLineY = borderBounds.Y - (Thickness.Top - 1);
  187. titleY = topTitleLineY + 1;
  188. titleBarsLength = 3;
  189. sideLineLength++;
  190. }
  191. // ┌────┐
  192. //┌┘View└
  193. //│
  194. if (Thickness.Top > 3)
  195. {
  196. topTitleLineY = borderBounds.Y - 2;
  197. titleY = topTitleLineY + 1;
  198. titleBarsLength = 3;
  199. sideLineLength++;
  200. }
  201. }
  202. if (canDrawBorder && Thickness.Top > 0 && maxTitleWidth > 0 && !string.IsNullOrEmpty (Parent?.Title))
  203. {
  204. Parent.TitleTextFormatter.Draw (
  205. new (borderBounds.X + 2, titleY, maxTitleWidth, 1),
  206. Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor (),
  207. Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetHotNormalColor ());
  208. }
  209. if (canDrawBorder && LineStyle != LineStyle.None)
  210. {
  211. LineCanvas lc = Parent?.LineCanvas;
  212. bool drawTop = Thickness.Top > 0 && Frame.Width > 1 && Frame.Height > 1;
  213. bool drawLeft = Thickness.Left > 0 && (Frame.Height > 1 || Thickness.Top == 0);
  214. bool drawBottom = Thickness.Bottom > 0 && Frame.Width > 1;
  215. bool drawRight = Thickness.Right > 0 && (Frame.Height > 1 || Thickness.Top == 0);
  216. Attribute prevAttr = Driver.GetAttribute ();
  217. if (ColorScheme is { })
  218. {
  219. Driver.SetAttribute (GetNormalColor ());
  220. }
  221. else
  222. {
  223. Driver.SetAttribute (Parent.GetNormalColor ());
  224. }
  225. if (drawTop)
  226. {
  227. // ╔╡Title╞═════╗
  228. // ╔╡╞═════╗
  229. if (borderBounds.Width < 4 || string.IsNullOrEmpty (Parent?.Title))
  230. {
  231. // ╔╡╞╗ should be ╔══╗
  232. lc.AddLine (
  233. new (borderBounds.Location.X, titleY),
  234. borderBounds.Width,
  235. Orientation.Horizontal,
  236. LineStyle,
  237. Driver.GetAttribute ()
  238. );
  239. }
  240. else
  241. {
  242. // ┌────┐
  243. //┌┘View└
  244. //│
  245. if (Thickness.Top == 2)
  246. {
  247. lc.AddLine (
  248. new (borderBounds.X + 1, topTitleLineY),
  249. Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
  250. Orientation.Horizontal,
  251. LineStyle,
  252. Driver.GetAttribute ()
  253. );
  254. }
  255. // ┌────┐
  256. //┌┘View└
  257. //│
  258. if (borderBounds.Width >= 4 && Thickness.Top > 2)
  259. {
  260. lc.AddLine (
  261. new (borderBounds.X + 1, topTitleLineY),
  262. Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
  263. Orientation.Horizontal,
  264. LineStyle,
  265. Driver.GetAttribute ()
  266. );
  267. lc.AddLine (
  268. new (borderBounds.X + 1, topTitleLineY + 2),
  269. Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
  270. Orientation.Horizontal,
  271. LineStyle,
  272. Driver.GetAttribute ()
  273. );
  274. }
  275. // ╔╡Title╞═════╗
  276. // Add a short horiz line for ╔╡
  277. lc.AddLine (
  278. new (borderBounds.Location.X, titleY),
  279. 2,
  280. Orientation.Horizontal,
  281. LineStyle,
  282. Driver.GetAttribute ()
  283. );
  284. // Add a vert line for ╔╡
  285. lc.AddLine (
  286. new (borderBounds.X + 1, topTitleLineY),
  287. titleBarsLength,
  288. Orientation.Vertical,
  289. LineStyle.Single,
  290. Driver.GetAttribute ()
  291. );
  292. // Add a vert line for ╞
  293. lc.AddLine (
  294. new (
  295. borderBounds.X
  296. + 1
  297. + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2)
  298. - 1,
  299. topTitleLineY
  300. ),
  301. titleBarsLength,
  302. Orientation.Vertical,
  303. LineStyle.Single,
  304. Driver.GetAttribute ()
  305. );
  306. // Add the right hand line for ╞═════╗
  307. lc.AddLine (
  308. new (
  309. borderBounds.X
  310. + 1
  311. + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2)
  312. - 1,
  313. titleY
  314. ),
  315. borderBounds.Width - Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
  316. Orientation.Horizontal,
  317. LineStyle,
  318. Driver.GetAttribute ()
  319. );
  320. }
  321. }
  322. if (drawLeft)
  323. {
  324. lc.AddLine (
  325. new (borderBounds.Location.X, titleY),
  326. sideLineLength,
  327. Orientation.Vertical,
  328. LineStyle,
  329. Driver.GetAttribute ()
  330. );
  331. }
  332. if (drawBottom)
  333. {
  334. lc.AddLine (
  335. new (borderBounds.X, borderBounds.Y + borderBounds.Height - 1),
  336. borderBounds.Width,
  337. Orientation.Horizontal,
  338. LineStyle,
  339. Driver.GetAttribute ()
  340. );
  341. }
  342. if (drawRight)
  343. {
  344. lc.AddLine (
  345. new (borderBounds.X + borderBounds.Width - 1, titleY),
  346. sideLineLength,
  347. Orientation.Vertical,
  348. LineStyle,
  349. Driver.GetAttribute ()
  350. );
  351. }
  352. Driver.SetAttribute (prevAttr);
  353. // TODO: This should be moved to LineCanvas as a new BorderStyle.Ruler
  354. if ((ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FrameRuler)
  355. == ConsoleDriver.DiagnosticFlags.FrameRuler)
  356. {
  357. // Top
  358. var hruler = new Ruler { Length = screenBounds.Width, Orientation = Orientation.Horizontal };
  359. if (drawTop)
  360. {
  361. hruler.Draw (new (screenBounds.X, screenBounds.Y));
  362. }
  363. // Redraw title
  364. if (drawTop && maxTitleWidth > 0 && !string.IsNullOrEmpty (Parent?.Title))
  365. {
  366. Parent.TitleTextFormatter.Draw (
  367. new (borderBounds.X + 2, titleY, maxTitleWidth, 1),
  368. Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor (),
  369. Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor ());
  370. }
  371. //Left
  372. var vruler = new Ruler { Length = screenBounds.Height - 2, Orientation = Orientation.Vertical };
  373. if (drawLeft)
  374. {
  375. vruler.Draw (new (screenBounds.X, screenBounds.Y + 1), 1);
  376. }
  377. // Bottom
  378. if (drawBottom)
  379. {
  380. hruler.Draw (new (screenBounds.X, screenBounds.Y + screenBounds.Height - 1));
  381. }
  382. // Right
  383. if (drawRight)
  384. {
  385. vruler.Draw (new (screenBounds.X + screenBounds.Width - 1, screenBounds.Y + 1), 1);
  386. }
  387. }
  388. }
  389. //base.OnDrawContent (contentArea);
  390. }
  391. }