Implement basic rendering
This commit is contained in:
parent
7103e6f659
commit
5aa718bea4
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user