Implement basic rendering

This commit is contained in:
Kovid Goyal 2016-10-20 06:50:50 +05:30
parent 7103e6f659
commit 5aa718bea4
3 changed files with 69 additions and 10 deletions

View File

@ -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:

View File

@ -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

View File

@ -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)