terminal_painters.dart 11 KB

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