diff --git a/example/lib/main.dart b/example/lib/main.dart index 586f829..188d5a6 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -42,6 +42,8 @@ class Home extends StatefulWidget { } class _HomeState extends State { + final focusNode = FocusNode(); + final terminal = Terminal( maxLines: 10000, ); @@ -94,21 +96,49 @@ class _HomeState extends State { child: TerminalView( terminal, controller: terminalController, - autofocus: true, - backgroundOpacity: 0.7, + //autofocus: true, + focusNode: focusNode, + theme: TerminalTheme( + //cursor: Color(0XAAAEAFAD), + //cursor: Colors.yellow, + //selection: Color(0XFFFFFF40), + //selection: Colors.red, + foreground: Color(0XFFCCCCCC), + background: Color(0XFF1E1E1E), + black: Colors.black, + red: Colors.red, + green: Colors.green, + yellow: Colors.yellow, + blue: Colors.blue, + magenta: Colors.purple, + cyan: Colors.cyan, + white: Colors.white, + brightBlack: Colors.black, + brightRed: Colors.red[300]!, + brightGreen: Colors.green[300]!, + brightYellow: Colors.yellow[300]!, + brightBlue: Colors.blue[300]!, + brightMagenta: Colors.purple[300]!, + brightCyan: Colors.cyan[300]!, + brightWhite: Colors.white, + searchHitBackground: Color(0XFFFFFF2B), + searchHitBackgroundCurrent: Color(0XFF31FF26), + searchHitForeground: Color(0XFF000000), + ), onSecondaryTapDown: (details, offset) async { - final selection = terminalController.selection; - if (selection != null) { - final text = terminal.buffer.getText(selection); - terminalController.clearSelection(); - await Clipboard.setData(ClipboardData(text: text)); - } else { - final data = await Clipboard.getData('text/plain'); - final text = data?.text; - if (text != null) { - terminal.paste(text); - } - } + focusNode.unfocus(); + // final selection = terminalController.selection; + // if (selection != null) { + // final text = terminal.buffer.getText(selection); + // terminalController.clearSelection(); + // await Clipboard.setData(ClipboardData(text: text)); + // } else { + // final data = await Clipboard.getData('text/plain'); + // final text = data?.text; + // if (text != null) { + // terminal.paste(text); + // } + // } }, ), ), diff --git a/lib/src/ui/render.dart b/lib/src/ui/render.dart index de2a2f7..b0def8f 100644 --- a/lib/src/ui/render.dart +++ b/lib/src/ui/render.dart @@ -428,12 +428,7 @@ class RenderTerminal extends RenderBox with RelayoutWhenSystemFontsChangeMixin { } if (_shouldShowCursor) { - _painter.paintCursor( - canvas, - offset + cursorOffset, - cursorType: _cursorType, - hasFocus: _focusNode.hasFocus, - ); + _paintCursor(canvas, cursorOffset); } } @@ -454,6 +449,51 @@ class RenderTerminal extends RenderBox with RelayoutWhenSystemFontsChangeMixin { } } + /// Paints the cursor based on the current cursor type. + void _paintCursor(Canvas canvas, Offset offset) { + var color = _theme.cursor; + if (color == null) { + final x = _terminal.buffer.cursorX; + final y = _terminal.buffer.absoluteCursorY; + if (_controller.selection?.contains(CellOffset(x, y)) == true) { + color = _theme.background; + } else { + color = _theme.foreground; + } + } + + final paint = Paint() + ..color = color + ..strokeWidth = 1; + + if (!_focusNode.hasFocus) { + paint.style = PaintingStyle.stroke; + canvas.drawRect(offset & _charSize, paint); + return; + } + + if (_theme.cursor != null) { + switch (_cursorType) { + case TerminalCursorType.block: + paint.style = PaintingStyle.fill; + canvas.drawRect(offset & _charSize, paint); + return; + case TerminalCursorType.underline: + return canvas.drawLine( + Offset(offset.dx, _charSize.height - 1), + Offset(offset.dx + _charSize.width, _charSize.height - 1), + paint, + ); + case TerminalCursorType.verticalBar: + return canvas.drawLine( + Offset(offset.dx, 0), + Offset(offset.dx, _charSize.height), + paint, + ); + } + } + } + /// Paints the text that is currently being composed in IME to [canvas] at /// [offset]. [offset] is usually the cursor position. void _paintComposingText(Canvas canvas, Offset offset) { @@ -485,6 +525,50 @@ class RenderTerminal extends RenderBox with RelayoutWhenSystemFontsChangeMixin { canvas.drawParagraph(paragraph, Offset(0, offset.dy)); } + void _toggleInverse(CellData cellData) { + if (cellData.flags & CellFlags.inverse == 0) { + cellData.flags |= CellFlags.inverse; + } else { + cellData.flags &= ~CellFlags.inverse; + } + } + + /// 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, int y, Offset offset) { + final line = _terminal.buffer.lines[y]; + final cellData = CellData.empty(); + final cellWidth = _charSize.width; + + final visibleCells = size.width ~/ cellWidth + 1; + final effectCells = min(visibleCells, line.length); + + for (var x = 0; x < effectCells; x++) { + line.getCellData(x, cellData); + + if (_theme.selection == null && + _controller.selection?.contains(CellOffset(x, y)) == true) { + _toggleInverse(cellData); + } + if (_theme.cursor == null && + _focusNode.hasFocus && + _terminal.buffer.absoluteCursorY == y && + _terminal.buffer.cursorX == x) { + _toggleInverse(cellData); + } + + final charWidth = cellData.content >> CellContent.widthShift; + final cellOffset = offset.translate(x * cellWidth, 0); + + _paintCellBackground(canvas, cellOffset, cellData); + _paintCellForeground(canvas, cellOffset, cellData); + + if (charWidth == 2) { + x++; + } + } + } + void _paintSelection( Canvas canvas, BufferRange selection,