terminal_painters.dart 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. import 'dart:math';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter/widgets.dart';
  4. import 'package:xterm/buffer/cell_flags.dart';
  5. import 'package:xterm/buffer/line/line.dart';
  6. import 'package:xterm/mouse/position.dart';
  7. import 'package:xterm/terminal/terminal_search.dart';
  8. import 'package:xterm/terminal/terminal_ui_interaction.dart';
  9. import 'package:xterm/theme/terminal_style.dart';
  10. import 'package:xterm/util/bit_flags.dart';
  11. import 'cache.dart';
  12. import 'char_size.dart';
  13. class TerminalPainter extends CustomPainter {
  14. TerminalPainter({
  15. required this.terminal,
  16. required this.style,
  17. required this.charSize,
  18. required this.textLayoutCache,
  19. });
  20. final TerminalUiInteraction terminal;
  21. final TerminalStyle style;
  22. final CellSize charSize;
  23. final TextLayoutCache textLayoutCache;
  24. @override
  25. void paint(Canvas canvas, Size size) {
  26. _paintBackground(canvas);
  27. _paintText(canvas);
  28. _paintUserSearchResult(canvas, size);
  29. _paintSelection(canvas);
  30. }
  31. void _paintBackground(Canvas canvas) {
  32. final lines = terminal.getVisibleLines();
  33. for (var row = 0; row < lines.length; row++) {
  34. final line = lines[row];
  35. final offsetY = row * charSize.cellHeight;
  36. final cellCount = terminal.terminalWidth;
  37. for (var col = 0; col < cellCount; col++) {
  38. final cellWidth = line.cellGetWidth(col);
  39. if (cellWidth == 0) {
  40. continue;
  41. }
  42. final cellFgColor = line.cellGetFgColor(col);
  43. final cellBgColor = line.cellGetBgColor(col);
  44. final effectBgColor = line.cellHasFlag(col, CellFlags.inverse)
  45. ? cellFgColor
  46. : cellBgColor;
  47. if (effectBgColor == 0x00) {
  48. continue;
  49. }
  50. // when a program reports black as background then it "really" means transparent
  51. if (effectBgColor == 0xFF000000) {
  52. continue;
  53. }
  54. final offsetX = col * charSize.cellWidth;
  55. final effectWidth = charSize.cellWidth * cellWidth + 1;
  56. final effectHeight = charSize.cellHeight + 1;
  57. // background color is already painted with opacity by the Container of
  58. // TerminalPainter so wo don't need to fallback to
  59. // terminal.theme.background here.
  60. final paint = Paint()..color = Color(effectBgColor);
  61. canvas.drawRect(
  62. Rect.fromLTWH(offsetX, offsetY, effectWidth, effectHeight),
  63. paint,
  64. );
  65. }
  66. }
  67. }
  68. void _paintUserSearchResult(Canvas canvas, Size size) {
  69. final searchResult = terminal.userSearchResult;
  70. //when there is no ongoing user search then directly return
  71. if (!terminal.isUserSearchActive) {
  72. return;
  73. }
  74. //make everything dim so that the search result can be seen better
  75. final dimPaint = Paint()
  76. ..color = Color(terminal.theme.background).withAlpha(128)
  77. ..style = PaintingStyle.fill;
  78. canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), dimPaint);
  79. for (int i = 1; i <= searchResult.allHits.length; i++) {
  80. _paintSearchHit(canvas, searchResult.allHits[i - 1], i);
  81. }
  82. }
  83. void _paintSearchHit(Canvas canvas, TerminalSearchHit hit, int hitNum) {
  84. //check if the hit is visible
  85. if (hit.startLineIndex >=
  86. terminal.scrollOffsetFromTop + terminal.terminalHeight ||
  87. hit.endLineIndex < terminal.scrollOffsetFromTop) {
  88. return;
  89. }
  90. final paint = Paint()
  91. ..color = Color(terminal.currentSearchHit == hitNum
  92. ? terminal.theme.searchHitBackgroundCurrent
  93. : terminal.theme.searchHitBackground)
  94. ..style = PaintingStyle.fill;
  95. if (hit.startLineIndex == hit.endLineIndex) {
  96. final double y =
  97. (hit.startLineIndex.toDouble() - terminal.scrollOffsetFromTop) *
  98. charSize.cellHeight;
  99. final startX = charSize.cellWidth * hit.startIndex;
  100. final endX = charSize.cellWidth * hit.endIndex;
  101. canvas.drawRect(
  102. Rect.fromLTRB(startX, y, endX, y + charSize.cellHeight), paint);
  103. } else {
  104. //draw first row: start - line end
  105. final double yFirstRow =
  106. (hit.startLineIndex.toDouble() - terminal.scrollOffsetFromTop) *
  107. charSize.cellHeight;
  108. final startXFirstRow = charSize.cellWidth * hit.startIndex;
  109. final endXFirstRow = charSize.cellWidth * terminal.terminalWidth;
  110. canvas.drawRect(
  111. Rect.fromLTRB(startXFirstRow, yFirstRow, endXFirstRow,
  112. yFirstRow + charSize.cellHeight),
  113. paint);
  114. //draw middle rows
  115. final middleRowCount = hit.endLineIndex - hit.startLineIndex - 1;
  116. if (middleRowCount > 0) {
  117. final startYMiddleRows =
  118. (hit.startLineIndex + 1 - terminal.scrollOffsetFromTop) *
  119. charSize.cellHeight;
  120. final startXMiddleRows = 0.toDouble();
  121. final endYMiddleRows = min(
  122. hit.endLineIndex - terminal.scrollOffsetFromTop,
  123. terminal.terminalHeight) *
  124. charSize.cellHeight;
  125. final endXMiddleRows = terminal.terminalWidth * charSize.cellWidth;
  126. canvas.drawRect(
  127. Rect.fromLTRB(startXMiddleRows, startYMiddleRows, endXMiddleRows,
  128. endYMiddleRows),
  129. paint);
  130. }
  131. //draw end row: line start - end
  132. if (hit.endLineIndex - terminal.scrollOffsetFromTop <
  133. terminal.terminalHeight) {
  134. final startXEndRow = 0.toDouble();
  135. final startYEndRow = (hit.endLineIndex - terminal.scrollOffsetFromTop) *
  136. charSize.cellHeight;
  137. final endXEndRow = hit.endIndex * charSize.cellWidth;
  138. final endYEndRow = startYEndRow + charSize.cellHeight;
  139. canvas.drawRect(
  140. Rect.fromLTRB(startXEndRow, startYEndRow, endXEndRow, endYEndRow),
  141. paint);
  142. }
  143. }
  144. final visibleLines = terminal.getVisibleLines();
  145. //paint text
  146. for (var rawRow = hit.startLineIndex;
  147. rawRow <= hit.endLineIndex;
  148. rawRow++) {
  149. final start = rawRow == hit.startLineIndex ? hit.startIndex : 0;
  150. final end =
  151. rawRow == hit.endLineIndex ? hit.endIndex : terminal.terminalWidth;
  152. final row = rawRow - terminal.scrollOffsetFromTop;
  153. final offsetY = row * charSize.cellHeight;
  154. if (row >= visibleLines.length || row < 0) {
  155. continue;
  156. }
  157. final line = visibleLines[row];
  158. for (var col = start; col < end; col++) {
  159. final offsetX = col * charSize.cellWidth;
  160. _paintCell(
  161. canvas,
  162. line,
  163. col,
  164. offsetX,
  165. offsetY,
  166. fgColorOverride: terminal.theme.searchHitForeground,
  167. bgColorOverride: terminal.theme.searchHitForeground,
  168. );
  169. }
  170. }
  171. }
  172. void _paintSelection(Canvas canvas) {
  173. final paint = Paint()..color = Colors.white.withOpacity(0.3);
  174. for (var y = 0; y < terminal.terminalHeight; y++) {
  175. final offsetY = y * charSize.cellHeight;
  176. final absoluteY = terminal.convertViewLineToRawLine(y) -
  177. terminal.scrollOffsetFromBottom;
  178. for (var x = 0; x < terminal.terminalWidth; x++) {
  179. var cellCount = 0;
  180. while (
  181. (terminal.selection?.contains(Position(x + cellCount, absoluteY)) ??
  182. false) &&
  183. x + cellCount < terminal.terminalWidth) {
  184. cellCount++;
  185. }
  186. if (cellCount == 0) {
  187. continue;
  188. }
  189. final offsetX = x * charSize.cellWidth;
  190. final effectWidth = cellCount * charSize.cellWidth;
  191. final effectHeight = charSize.cellHeight;
  192. canvas.drawRect(
  193. Rect.fromLTWH(offsetX, offsetY, effectWidth, effectHeight),
  194. paint,
  195. );
  196. x += cellCount;
  197. }
  198. }
  199. }
  200. void _paintText(Canvas canvas) {
  201. final lines = terminal.getVisibleLines();
  202. for (var row = 0; row < lines.length; row++) {
  203. final line = lines[row];
  204. final offsetY = row * charSize.cellHeight;
  205. // final cellCount = math.min(terminal.viewWidth, line.length);
  206. final cellCount = terminal.terminalWidth;
  207. for (var col = 0; col < cellCount; col++) {
  208. final width = line.cellGetWidth(col);
  209. if (width == 0) {
  210. continue;
  211. }
  212. final offsetX = col * charSize.cellWidth;
  213. _paintCell(
  214. canvas,
  215. line,
  216. col,
  217. offsetX,
  218. offsetY,
  219. );
  220. }
  221. }
  222. }
  223. int _getColor(int colorCode) {
  224. return (colorCode == 0) ? 0xFF000000 : colorCode;
  225. }
  226. void _paintCell(
  227. Canvas canvas,
  228. BufferLine line,
  229. int cell,
  230. double offsetX,
  231. double offsetY, {
  232. int? fgColorOverride,
  233. int? bgColorOverride,
  234. }) {
  235. final codePoint = line.cellGetContent(cell);
  236. final fgColor = fgColorOverride ?? _getColor(line.cellGetFgColor(cell));
  237. final bgColor = bgColorOverride ?? _getColor(line.cellGetBgColor(cell));
  238. final flags = line.cellGetFlags(cell);
  239. if (codePoint == 0 || flags.hasFlag(CellFlags.invisible)) {
  240. return;
  241. }
  242. // final cellHash = line.cellGetHash(cell);
  243. final cellHash = hashValues(codePoint, fgColor, bgColor, flags);
  244. var character = textLayoutCache.getLayoutFromCache(cellHash);
  245. if (character != null) {
  246. canvas.drawParagraph(character, Offset(offsetX, offsetY));
  247. return;
  248. }
  249. final cellColor = flags.hasFlag(CellFlags.inverse) ? bgColor : fgColor;
  250. var color = Color(cellColor);
  251. if (flags & CellFlags.faint != 0) {
  252. color = color.withOpacity(0.5);
  253. }
  254. final styleToUse = PaintHelper.getStyleToUse(
  255. style,
  256. color,
  257. bold: flags.hasFlag(CellFlags.bold),
  258. italic: flags.hasFlag(CellFlags.italic),
  259. underline: flags.hasFlag(CellFlags.underline),
  260. );
  261. character = textLayoutCache.performAndCacheLayout(
  262. String.fromCharCode(codePoint), styleToUse, cellHash);
  263. canvas.drawParagraph(character, Offset(offsetX, offsetY));
  264. }
  265. @override
  266. bool shouldRepaint(CustomPainter oldDelegate) {
  267. /// paint only when the terminal has changed since last paint.
  268. return terminal.dirty;
  269. }
  270. }
  271. class CursorPainter extends CustomPainter {
  272. final bool visible;
  273. final CellSize charSize;
  274. final bool focused;
  275. final bool blinkVisible;
  276. final int cursorColor;
  277. final int textColor;
  278. final String composingString;
  279. final TextLayoutCache textLayoutCache;
  280. final TerminalStyle style;
  281. CursorPainter({
  282. required this.visible,
  283. required this.charSize,
  284. required this.focused,
  285. required this.blinkVisible,
  286. required this.cursorColor,
  287. required this.textColor,
  288. required this.composingString,
  289. required this.textLayoutCache,
  290. required this.style,
  291. });
  292. @override
  293. void paint(Canvas canvas, Size size) {
  294. bool isVisible =
  295. visible && (blinkVisible || composingString != '' || !focused);
  296. if (isVisible) {
  297. _paintCursor(canvas);
  298. }
  299. }
  300. @override
  301. bool shouldRepaint(covariant CustomPainter oldDelegate) {
  302. if (oldDelegate is CursorPainter) {
  303. return blinkVisible != oldDelegate.blinkVisible ||
  304. focused != oldDelegate.focused ||
  305. visible != oldDelegate.visible ||
  306. charSize.cellWidth != oldDelegate.charSize.cellWidth ||
  307. charSize.cellHeight != oldDelegate.charSize.cellHeight ||
  308. composingString != oldDelegate.composingString;
  309. }
  310. return true;
  311. }
  312. void _paintCursor(Canvas canvas) {
  313. final paint = Paint()
  314. ..color = Color(cursorColor)
  315. ..strokeWidth = focused ? 0.0 : 1.0
  316. ..style = focused ? PaintingStyle.fill : PaintingStyle.stroke;
  317. canvas.drawRect(
  318. Rect.fromLTWH(0, 0, charSize.cellWidth, charSize.cellHeight), paint);
  319. if (composingString != '') {
  320. final styleToUse = PaintHelper.getStyleToUse(style, Color(textColor));
  321. final character = textLayoutCache.performAndCacheLayout(
  322. composingString, styleToUse, null);
  323. canvas.drawParagraph(character, Offset(0, 0));
  324. }
  325. }
  326. }
  327. class PaintHelper {
  328. static TextStyle getStyleToUse(
  329. TerminalStyle style,
  330. Color color, {
  331. bool bold = false,
  332. bool italic = false,
  333. bool underline = false,
  334. }) {
  335. return (style.textStyleProvider != null)
  336. ? style.textStyleProvider!(
  337. color: color,
  338. fontSize: style.fontSize,
  339. fontWeight: bold && !style.ignoreBoldFlag
  340. ? FontWeight.bold
  341. : FontWeight.normal,
  342. fontStyle: italic ? FontStyle.italic : FontStyle.normal,
  343. decoration:
  344. underline ? TextDecoration.underline : TextDecoration.none,
  345. )
  346. : TextStyle(
  347. color: color,
  348. fontSize: style.fontSize,
  349. fontWeight: bold && !style.ignoreBoldFlag
  350. ? FontWeight.bold
  351. : FontWeight.normal,
  352. fontStyle: italic ? FontStyle.italic : FontStyle.normal,
  353. decoration:
  354. underline ? TextDecoration.underline : TextDecoration.none,
  355. fontFamily: 'monospace',
  356. fontFamilyFallback: style.fontFamily,
  357. );
  358. }
  359. }