From 5aa718bea49d0a5e4f979501a4a7cf328a9f4a2c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 20 Oct 2016 06:50:50 +0530 Subject: [PATCH] Implement basic rendering --- kitty/boss.py | 9 +++++-- kitty/config.py | 4 ++- kitty/term.py | 66 +++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 69 insertions(+), 10 deletions(-) diff --git a/kitty/boss.py b/kitty/boss.py index ebf1febac..d8c891e1c 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -10,6 +10,8 @@ from PyQt5.QtCore import QObject, QSocketNotifier from .screen import Screen from .term import TerminalWidget from .utils import resize_pty, hangup, create_pty +from .tracker import ChangeTracker +from pyte.streams import Stream class Boss(QObject): @@ -22,9 +24,11 @@ class Boss(QObject): self.write_notifier = QSocketNotifier(create_pty()[0], QSocketNotifier.Write, self) self.write_notifier.setEnabled(False) self.write_notifier.activated.connect(self.write_ready) - self.screen = s = Screen(opts, parent=self) + self.tracker = ChangeTracker(self) + self.screen = s = Screen(opts, self.tracker, parent=self) + self.stream = Stream(s) s.write_to_child.connect(self.write_to_child) - self.term = TerminalWidget(opts, self.screen.linebuf, parent) + self.term = TerminalWidget(opts, self.tracker, self.screen.linebuf, parent) self.term.relayout_lines.connect(self.relayout_lines) resize_pty(self.screen.columns, self.screen.lines) @@ -38,6 +42,7 @@ class Boss(QObject): # EOF self.parent().child_process_died() return + self.stream.feed(data) def write_ready(self, write_fd): while self.write_buf: diff --git a/kitty/config.py b/kitty/config.py index 09953926d..19a27830a 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -26,6 +26,7 @@ def to_font_size(x): type_map = { 'scrollback_lines': int, 'font_size': to_font_size, + 'cursor_opacity': float, } for name in 'foreground foreground_bold background cursor'.split(): type_map[name] = to_qcolor @@ -35,7 +36,8 @@ for line in ''' term xterm-kitty foreground #dddddd foreground_bold #ffffff -cursor #dddddd +cursor #eeeeee +cursor_opacity 0.8 background #000000 font_family monospace font_size 10.0 diff --git a/kitty/term.py b/kitty/term.py index 7a8387e31..10d3aae96 100644 --- a/kitty/term.py +++ b/kitty/term.py @@ -4,13 +4,15 @@ from typing import Tuple, Iterator, Union, Sequence -from PyQt5.QtCore import pyqtSignal, QTimer, QRect +from PyQt5.QtCore import pyqtSignal, QTimer, QRect, Qt from PyQt5.QtGui import QColor, QPainter, QFont, QFontMetrics, QRegion, QPen from PyQt5.QtWidgets import QWidget from .config import build_ansi_color_tables, Options -from .data_types import Line +from .data_types import Line, Cursor from .utils import set_current_font_metrics +from .tracker import ChangeTracker +from .screen import wrap_cursor_position def ascii_width(fm: QFontMetrics) -> int: @@ -26,9 +28,12 @@ class TerminalWidget(QWidget): cells_per_line = 80 lines_per_screen = 24 - def __init__(self, opts: Options, linebuf: Sequence[Line], parent: QWidget=None): + def __init__(self, opts: Options, tracker: ChangeTracker, linebuf: Sequence[Line], parent: QWidget=None): QWidget.__init__(self, parent) + self.setFocusPolicy(Qt.WheelFocus) + tracker.dirtied.connect(self.update_screen) self.linebuf = linebuf + self.cursor = Cursor() self.setAutoFillBackground(True) self.apply_opts(opts) self.debounce_resize_timer = t = QTimer(self) @@ -53,6 +58,8 @@ class TerminalWidget(QWidget): self.cell_width = ascii_width(fm) set_current_font_metrics(fm, self.cell_width) self.baseline_offset = fm.ascent() + self.cursor_color = c = QColor(opts.cursor) + c.setAlphaF(opts.cursor_opacity) self.do_layout() def do_layout(self): @@ -70,6 +77,31 @@ class TerminalWidget(QWidget): def resizeEvent(self, ev): self.debounce_resize_timer.start() + def update_screen(self, changes): + old_cursor, self.cursor = self.cursor, changes['cursor'] or self.cursor + if changes['screen']: + self.update() + return + cell_positions, line_positions, cell_width, cell_height = self.cell_positions, self.line_positions, self.cell_width, self.cell_height + old_x, old_y = wrap_cursor_position(old_cursor.x, old_cursor.y, len(line_positions), len(cell_positions)) + del old_cursor + rects = [] + for lnum in changes['lines']: + rects.append(QRect(cell_positions[0], line_positions[lnum], cell_positions[-1] - cell_positions[0] + cell_width, cell_height)) + old_cursor_added = old_y in changes['lines'] + for lnum, ranges in changes['cells'].items(): + for start, stop in ranges: + rects.append(QRect(cell_positions[start], line_positions[lnum], cell_width * (stop - start + 1), cell_height)) + if not old_cursor_added and old_y == lnum and (start <= old_x <= stop): + old_cursor_added = True + rects.sort(key=lambda r: (r.y(), r.x())) + reg = QRegion() + for r in rects: + reg += r + if not old_cursor_added: + reg += QRect(cell_positions[old_x], line_positions[old_y], cell_width, cell_height) + self.update(reg) + def dirty_lines(self, region: QRegion) -> Iterator[Tuple[int, QRegion]]: w = self.width() - 2 * self.hmargin for i, y in enumerate(self.line_positions): @@ -93,24 +125,44 @@ class TerminalWidget(QWidget): return r = ev.region() p = QPainter(self) + p.setRenderHints(p.TextAntialiasing | p.Antialiasing) + + try: + self.paint_cursor(p) + except Exception: + import traceback + traceback.print_exc() + for lnum, line_region in self.dirty_lines(r): line = self.line(lnum) if line is not None: ypos = self.line_positions[lnum] for cnum in self.dirty_cells(ypos, line_region): p.save() - self.paint_cell(p, line, cnum, ypos) + try: + self.paint_cell(p, line, cnum, ypos) + except Exception: + import traceback + traceback.print_exc() p.restore() + def paint_cursor(self, painter): + x, y = wrap_cursor_position(self.cursor.x, self.cursor.y, len(self.line_positions), len(self.cell_positions)) + r = QRect(self.cell_positions[x], self.line_positions[y], self.cell_width, self.cell_height) + if self.hasFocus(): + painter.fillRect(r, self.cursor_color) + else: + painter.setPen(QPen(self.cursor_color)) + painter.drawRect(r) + def paint_cell(self, painter: QPainter, line: Line, col: int, y: int) -> None: - x = self.cell_positions[col] - c = line.cursor_from(x) + x, c = self.cell_positions[col], line.cursor_from(col) fg, bg, decoration_fg = c.colors() + text = line.text_at(col) if fg is not None: painter.setPen(QPen(fg)) if bg is not None: r = QRect(x, y, self.cell_width, self.cell_height) painter.fillRect(r, bg) - text = line.text_at(col) if text.rstrip(): painter.drawText(x, y + self.baseline_offset, text)