| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- import 'dart:ui';
- import 'package:flutter/painting.dart';
- import 'package:xterm/src/ui/palette_builder.dart';
- import 'package:xterm/src/ui/paragraph_cache.dart';
- import 'package:xterm/xterm.dart';
- /// Encapsulates the logic for painting various terminal elements.
- class TerminalPainter {
- TerminalPainter({
- required TerminalTheme theme,
- required TerminalStyle textStyle,
- required TextScaler textScaler,
- }) : _textStyle = textStyle,
- _theme = theme,
- _textScaler = textScaler;
- /// A lookup table from terminal colors to Flutter colors.
- late var _colorPalette = PaletteBuilder(_theme).build();
- /// Size of each character in the terminal.
- late var _cellSize = _measureCharSize();
- /// The cached for cells in the terminal. Should be cleared when the same
- /// cell no longer produces the same visual output. For example, when
- /// [_textStyle] is changed, or when the system font changes.
- final _paragraphCache = ParagraphCache(10240);
- TerminalStyle get textStyle => _textStyle;
- TerminalStyle _textStyle;
- set textStyle(TerminalStyle value) {
- if (value == _textStyle) return;
- _textStyle = value;
- _cellSize = _measureCharSize();
- _paragraphCache.clear();
- }
- TextScaler get textScaler => _textScaler;
- TextScaler _textScaler = TextScaler.linear(1.0);
- set textScaler(TextScaler value) {
- if (value == _textScaler) return;
- _textScaler = value;
- _cellSize = _measureCharSize();
- _paragraphCache.clear();
- }
- TerminalTheme get theme => _theme;
- TerminalTheme _theme;
- set theme(TerminalTheme value) {
- if (value == _theme) return;
- _theme = value;
- _colorPalette = PaletteBuilder(value).build();
- _paragraphCache.clear();
- }
- Size _measureCharSize() {
- const test = 'mmmmmmmmmm';
- final textStyle = _textStyle.toTextStyle();
- final builder = ParagraphBuilder(textStyle.getParagraphStyle());
- builder.pushStyle(
- textStyle.getTextStyle(textScaler: _textScaler),
- );
- builder.addText(test);
- final paragraph = builder.build();
- paragraph.layout(ParagraphConstraints(width: double.infinity));
- final result = Size(
- paragraph.maxIntrinsicWidth / test.length,
- paragraph.height,
- );
- paragraph.dispose();
- return result;
- }
- /// The size of each character in the terminal.
- Size get cellSize => _cellSize;
- /// When the set of font available to the system changes, call this method to
- /// clear cached state related to font rendering.
- void clearFontCache() {
- _cellSize = _measureCharSize();
- _paragraphCache.clear();
- }
- /// Paints the cursor based on the current cursor type.
- void paintCursor(
- Canvas canvas,
- Offset offset, {
- required TerminalCursorType cursorType,
- bool hasFocus = true,
- }) {
- final paint = Paint()
- ..color = _theme.cursor
- ..strokeWidth = 1;
- if (!hasFocus) {
- paint.style = PaintingStyle.stroke;
- canvas.drawRect(offset & _cellSize, paint);
- return;
- }
- switch (cursorType) {
- case TerminalCursorType.block:
- paint.style = PaintingStyle.fill;
- canvas.drawRect(offset & _cellSize, paint);
- return;
- case TerminalCursorType.underline:
- return canvas.drawLine(
- Offset(offset.dx, _cellSize.height - 1),
- Offset(offset.dx + _cellSize.width, _cellSize.height - 1),
- paint,
- );
- case TerminalCursorType.verticalBar:
- return canvas.drawLine(
- Offset(offset.dx, 0),
- Offset(offset.dx, _cellSize.height),
- paint,
- );
- }
- }
- @pragma('vm:prefer-inline')
- void paintHighlight(Canvas canvas, Offset offset, int length, Color color) {
- final endOffset =
- offset.translate(length * _cellSize.width, _cellSize.height);
- final paint = Paint()
- ..color = color
- ..strokeWidth = 1;
- canvas.drawRect(
- Rect.fromPoints(offset, endOffset),
- paint,
- );
- }
- /// Paints [line] to [canvas] at [offset]. The x offset of [offset] is usually
- /// 0, and the y offset is the top of the line.
- void paintLine(
- Canvas canvas,
- Offset offset,
- BufferLine line,
- ) {
- final cellData = CellData.empty();
- final cellWidth = _cellSize.width;
- for (var i = 0; i < line.length; i++) {
- line.getCellData(i, cellData);
- final charWidth = cellData.content >> CellContent.widthShift;
- final cellOffset = offset.translate(i * cellWidth, 0);
- paintCell(canvas, cellOffset, cellData);
- if (charWidth == 2) {
- i++;
- }
- }
- }
- @pragma('vm:prefer-inline')
- void paintCell(Canvas canvas, Offset offset, CellData cellData) {
- paintCellBackground(canvas, offset, cellData);
- paintCellForeground(canvas, offset, cellData);
- }
- /// Paints the character in the cell represented by [cellData] to [canvas] at
- /// [offset].
- @pragma('vm:prefer-inline')
- void paintCellForeground(Canvas canvas, Offset offset, CellData cellData) {
- final charCode = cellData.content & CellContent.codepointMask;
- if (charCode == 0) return;
- final cacheKey = cellData.getHash() ^ _textScaler.hashCode;
- var paragraph = _paragraphCache.getLayoutFromCache(cacheKey);
- if (paragraph == null) {
- final cellFlags = cellData.flags;
- var color = cellFlags & CellFlags.inverse == 0
- ? resolveForegroundColor(cellData.foreground)
- : resolveBackgroundColor(cellData.background);
- if (cellData.flags & CellFlags.faint != 0) {
- color = color.withOpacity(0.5);
- }
- final style = _textStyle.toTextStyle(
- color: color,
- bold: cellFlags & CellFlags.bold != 0,
- italic: cellFlags & CellFlags.italic != 0,
- underline: cellFlags & CellFlags.underline != 0,
- );
- // Flutter does not draw an underline below a space which is not between
- // other regular characters. As only single characters are drawn, this
- // will never produce an underline below a space in the terminal. As a
- // workaround the regular space CodePoint 0x20 is replaced with
- // the CodePoint 0xA0. This is a non breaking space and a underline can be
- // drawn below it.
- var char = String.fromCharCode(charCode);
- if (cellFlags & CellFlags.underline != 0 && charCode == 0x20) {
- char = String.fromCharCode(0xA0);
- }
- paragraph = _paragraphCache.performAndCacheLayout(
- char,
- style,
- _textScaler,
- cacheKey,
- );
- }
- canvas.drawParagraph(paragraph, offset);
- }
- /// Paints the background of a cell represented by [cellData] to [canvas] at
- /// [offset].
- @pragma('vm:prefer-inline')
- void paintCellBackground(Canvas canvas, Offset offset, CellData cellData) {
- late Color color;
- final colorType = cellData.background & CellColor.typeMask;
- if (cellData.flags & CellFlags.inverse != 0) {
- color = resolveForegroundColor(cellData.foreground);
- } else if (colorType == CellColor.normal) {
- return;
- } else {
- color = resolveBackgroundColor(cellData.background);
- }
- final paint = Paint()..color = color;
- final doubleWidth = cellData.content >> CellContent.widthShift == 2;
- final widthScale = doubleWidth ? 2 : 1;
- final size = Size(_cellSize.width * widthScale + 1, _cellSize.height);
- canvas.drawRect(offset & size, paint);
- }
- /// Get the effective foreground color for a cell from information encoded in
- /// [cellColor].
- @pragma('vm:prefer-inline')
- Color resolveForegroundColor(int cellColor) {
- final colorType = cellColor & CellColor.typeMask;
- final colorValue = cellColor & CellColor.valueMask;
- switch (colorType) {
- case CellColor.normal:
- return _theme.foreground;
- case CellColor.named:
- case CellColor.palette:
- return _colorPalette[colorValue];
- case CellColor.rgb:
- default:
- return Color(colorValue | 0xFF000000);
- }
- }
- /// Get the effective background color for a cell from information encoded in
- /// [cellColor].
- @pragma('vm:prefer-inline')
- Color resolveBackgroundColor(int cellColor) {
- final colorType = cellColor & CellColor.typeMask;
- final colorValue = cellColor & CellColor.valueMask;
- switch (colorType) {
- case CellColor.normal:
- return _theme.background;
- case CellColor.named:
- case CellColor.palette:
- return _colorPalette[colorValue];
- case CellColor.rgb:
- default:
- return Color(colorValue | 0xFF000000);
- }
- }
- }
|