mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-06 01:05:48 +02:00
Render to an offscreen buffer
That way we can move to threaded rendering if needed for improved performance
This commit is contained in:
274
kitty/render.py
Normal file
274
kitty/render.py
Normal file
@@ -0,0 +1,274 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from functools import lru_cache
|
||||
from collections import Counter, deque, defaultdict
|
||||
from itertools import chain
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSignal, Qt, QTimer, QRect
|
||||
from PyQt5.QtGui import QPixmap, QRegion, QPainter, QPen, QColor, QFontMetrics, QFont
|
||||
|
||||
from .config import build_ansi_color_tables, fg_color_table, bg_color_table
|
||||
from .data_types import Cursor, COL_SHIFT, COL_MASK, as_color
|
||||
from .screen import wrap_cursor_position
|
||||
from .tracker import merge_ranges
|
||||
from .utils import set_current_font_metrics
|
||||
|
||||
|
||||
def ascii_width(fm: QFontMetrics) -> int:
|
||||
ans = 0
|
||||
for i in range(32, 128):
|
||||
ans = max(ans, fm.widthChar(chr(i)))
|
||||
return ans
|
||||
|
||||
|
||||
@lru_cache(maxsize=2**11)
|
||||
def pixmap_for_text(text, color, default_fg, font, w, h, baseline):
|
||||
p = QPixmap(w, h)
|
||||
p.fill(Qt.transparent)
|
||||
fg = as_color(color & COL_MASK, fg_color_table()) or default_fg
|
||||
painter = QPainter(p)
|
||||
painter.setFont(font)
|
||||
painter.setPen(QPen(QColor(*fg)))
|
||||
painter.setRenderHints(QPainter.TextAntialiasing | QPainter.Antialiasing)
|
||||
painter.drawText(0, baseline, text)
|
||||
painter.end()
|
||||
return p
|
||||
|
||||
|
||||
class Renderer(QObject):
|
||||
|
||||
update_required = pyqtSignal()
|
||||
relayout_lines = pyqtSignal(object, object)
|
||||
cells_per_line = 80
|
||||
lines_per_screen = 24
|
||||
last_painted_cursor_at = 0, 0
|
||||
_has_focus = True
|
||||
|
||||
def __init__(self, screen, dpix, dpiy, parent=None):
|
||||
QObject.__init__(self, parent)
|
||||
self.dpix, self.dpiy = dpix, dpiy
|
||||
self.screen = screen
|
||||
screen.change_default_color.connect(self.change_default_color)
|
||||
self.bufpix = QPixmap(10, 10)
|
||||
self.pending_changes = deque()
|
||||
self.debounce_update_timer = t = QTimer(self)
|
||||
t.setSingleShot(True)
|
||||
t.setInterval(20)
|
||||
t.timeout.connect(self.do_render)
|
||||
self.cursor = Cursor()
|
||||
|
||||
def apply_opts(self, opts):
|
||||
pixmap_for_text.cache_clear()
|
||||
build_ansi_color_tables(opts)
|
||||
self.opts = opts
|
||||
self.default_bg = self.original_bg = QColor(opts.background)
|
||||
self.default_fg = self.original_fg = QColor(opts.foreground).getRgb()[:3]
|
||||
self.current_font = f = QFont(opts.font_family)
|
||||
f.setPointSizeF(opts.font_size)
|
||||
self.font_metrics = fm = QFontMetrics(f)
|
||||
self.cell_height = fm.lineSpacing()
|
||||
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)
|
||||
|
||||
def resize(self, size):
|
||||
self.bufpix = QPixmap(size)
|
||||
self.bufpix.fill(self.default_bg)
|
||||
previous, self.cells_per_line = self.cells_per_line, size.width() // self.cell_width
|
||||
previousl, self.lines_per_screen = self.lines_per_screen, size.height() // self.cell_height
|
||||
self.hmargin = (size.width() - self.cells_per_line * self.cell_width) // 2
|
||||
self.vmargin = (size.height() % self.cell_height) // 2
|
||||
self.line_positions = tuple(self.vmargin + i * self.cell_height for i in range(self.lines_per_screen))
|
||||
self.cell_positions = tuple(self.hmargin + i * self.cell_width for i in range(self.cells_per_line))
|
||||
self.line_width = self.cells_per_line * self.cell_width
|
||||
if (previous, previousl) != (self.cells_per_line, self.lines_per_screen):
|
||||
self.screen.resize(self.lines_per_screen, self.cells_per_line)
|
||||
self.relayout_lines.emit(self.cells_per_line, self.lines_per_screen)
|
||||
self.dirtied()
|
||||
|
||||
def dirtied(self):
|
||||
self.update_screen({'screen': True})
|
||||
|
||||
def size(self):
|
||||
return self.bufpix.size()
|
||||
|
||||
def render(self, painter):
|
||||
painter.drawPixmap(0, 0, self.bufpix)
|
||||
|
||||
def change_default_color(self, which, val):
|
||||
if which in ('fg', 'bg'):
|
||||
if not val:
|
||||
setattr(self, 'default_' + which, getattr(self, 'original_' + which))
|
||||
self.dirtied()
|
||||
else:
|
||||
val = QColor(val)
|
||||
if val.isValid():
|
||||
if which == 'fg':
|
||||
self.default_fg = val.getRgb()[:3]
|
||||
else:
|
||||
self.default_bg = val
|
||||
self.dirtied()
|
||||
|
||||
def update_screen(self, changes):
|
||||
self.pending_changes.append(changes)
|
||||
if not self.debounce_update_timer.isActive():
|
||||
self.debounce_update_timer.start()
|
||||
|
||||
def wrap_cursor_pos(self):
|
||||
self.cursorx, self.cursory = wrap_cursor_position(self.cursor.x, self.cursor.y, self.lines_per_screen, self.cells_per_line)
|
||||
|
||||
def set_has_focus(self, yes):
|
||||
if yes != self._has_focus:
|
||||
self._has_focus = yes
|
||||
self.wrap_cursor_pos()
|
||||
self.update_screen({'screen': False, 'lines': set(), 'cells': {self.cursory: {(self.cursorx, self.cursorx)}}})
|
||||
|
||||
def line(self, lnum):
|
||||
return self.screen.line(lnum)
|
||||
|
||||
def common_bg_color(self):
|
||||
c = Counter()
|
||||
for rdiv in range(1, 4):
|
||||
lnum = int(self.lines_per_screen * rdiv / 4)
|
||||
for cdiv in range(1, 4):
|
||||
cnum = int(self.cells_per_line * cdiv / 4)
|
||||
bgcol = self.line(lnum).bgcolor(cnum)
|
||||
c[bgcol] += 1
|
||||
return c.most_common(1)[0][0]
|
||||
|
||||
def do_render(self):
|
||||
dirty_lines = set()
|
||||
dirty_cell_ranges = defaultdict(set)
|
||||
screen_dirtied = False
|
||||
|
||||
while self.pending_changes:
|
||||
c = self.pending_changes.popleft()
|
||||
self.cursor = c.get('cursor') or self.cursor
|
||||
if not screen_dirtied:
|
||||
if c['screen']:
|
||||
screen_dirtied = True
|
||||
continue
|
||||
dirty_lines |= c['lines']
|
||||
for l, ranges in c['cells'].items():
|
||||
if l not in dirty_lines:
|
||||
for r in ranges:
|
||||
dirty_cell_ranges[l].add(r)
|
||||
|
||||
if screen_dirtied:
|
||||
dirty_cell_ranges = {}
|
||||
dirty_lines = tuple(range(self.lines_per_screen))
|
||||
else:
|
||||
dirty_cell_ranges = {l: tuple(merge_ranges(r)) for l, r in dirty_cell_ranges.items() if l not in dirty_lines}
|
||||
|
||||
self.paint(dirty_lines, dirty_cell_ranges)
|
||||
self.update_required.emit()
|
||||
|
||||
def calculate_dirty_region(self, dirty_lines, dirty_cell_ranges):
|
||||
ans = QRegion()
|
||||
for lnum in dirty_lines:
|
||||
ans += QRect(self.hmargin, self.line_positions[lnum], self.cell_width * self.cells_per_line, self.cell_height)
|
||||
for lnum, ranges in dirty_cell_ranges.items():
|
||||
y = self.line_positions[lnum]
|
||||
for start, stop in ranges:
|
||||
for cnum in range(start, stop + 1):
|
||||
ans += QRect(self.cell_positions[cnum], y, self.cell_width, self.cell_height)
|
||||
return ans
|
||||
|
||||
def paint(self, dirty_lines, dirty_cell_ranges):
|
||||
self.current_bgcol = self.common_bg_color()
|
||||
bg = self.default_bg
|
||||
if self.current_bgcol & 0xff:
|
||||
cbg = as_color(self.current_bgcol, bg_color_table())
|
||||
if cbg:
|
||||
bg = QColor(*cbg)
|
||||
self.wrap_cursor_pos()
|
||||
self.cursor_painted = False
|
||||
self.old_cursorx, self.old_cursory = self.last_painted_cursor_at
|
||||
self.cursor_moved = self.old_cursorx != self.cursorx or self.old_cursory != self.cursory
|
||||
region = self.calculate_dirty_region(dirty_lines, dirty_cell_ranges)
|
||||
if self.cursor_moved:
|
||||
r = QRect(self.cell_positions[self.old_cursorx], self.line_positions[self.old_cursory], self.cell_width, self.cell_height)
|
||||
if region.contains(r):
|
||||
self.cursor_moved = False
|
||||
else:
|
||||
region += r
|
||||
|
||||
p = QPainter(self.bufpix)
|
||||
p.save()
|
||||
p.setClipRegion(region)
|
||||
p.fillRect(self.bufpix.rect(), bg)
|
||||
p.restore()
|
||||
|
||||
for lnum in dirty_lines:
|
||||
self.paint_line(p, lnum, range(self.cells_per_line))
|
||||
|
||||
for lnum, ranges in dirty_cell_ranges.items():
|
||||
self.paint_line(p, lnum, chain.from_iterable(range(start, stop + 1) for start, stop in ranges))
|
||||
|
||||
if not self.cursor_painted:
|
||||
self.paint_cell(p, self.line(self.cursory), self.cursory, self.cursorx)
|
||||
|
||||
if self.cursor_moved:
|
||||
self.paint_cell(p, self.line(self.old_cursory), self.old_cursory, self.old_cursorx)
|
||||
|
||||
p.end()
|
||||
|
||||
def paint_line(self, painter, lnum, cell_range):
|
||||
line = self.line(lnum)
|
||||
for cnum in cell_range:
|
||||
self.paint_cell(painter, line, lnum, cnum)
|
||||
|
||||
def paint_cell(self, painter, line, lnum, cnum):
|
||||
paint_cursor = False
|
||||
if not self.cursor_painted:
|
||||
self.cursor_painted = paint_cursor = lnum == self.cursory and cnum == self.cursorx
|
||||
ch, attrs, colors = line.basic_cell_data(cnum)
|
||||
x, y = self.cell_positions[cnum], self.line_positions[lnum]
|
||||
bgcol = colors >> COL_SHIFT
|
||||
if bgcol != self.current_bgcol:
|
||||
bg = as_color(colors >> COL_SHIFT, bg_color_table())
|
||||
if bg is not None:
|
||||
r = QRect(x, y, self.cell_width, self.cell_height)
|
||||
painter.fillRect(r, QColor(*bg))
|
||||
if paint_cursor:
|
||||
self.paint_cursor(painter, cnum, lnum)
|
||||
if ch == 0 or ch == 32:
|
||||
# An empty cell
|
||||
pass
|
||||
else:
|
||||
text = chr(ch) + line.combining_chars.get(cnum, '')
|
||||
p = pixmap_for_text(text, colors, self.default_fg, self.current_font, self.cell_width * 2, self.cell_height, self.baseline_offset)
|
||||
painter.drawPixmap(x, y, p)
|
||||
|
||||
def paint_cursor(self, painter, x, y):
|
||||
self.last_painted_cursor_at = x, y
|
||||
r = QRect(self.cell_positions[x], self.line_positions[y], self.cell_width, self.cell_height)
|
||||
cc = self.cursor_color
|
||||
if self.cursor.color:
|
||||
q = QColor(self.cursor.color)
|
||||
if q.isValid():
|
||||
cc = q
|
||||
cc.setAlphaF(self.opts.cursor_opacity)
|
||||
|
||||
def width(w=2, vert=True):
|
||||
dpi = self.dpix if vert else self.dpiy
|
||||
return int(w * dpi / 72)
|
||||
|
||||
if self._has_focus:
|
||||
cs = self.cursor.shape or self.opts.cursor_shape
|
||||
if cs == 'block':
|
||||
painter.fillRect(r, cc)
|
||||
elif cs == 'beam':
|
||||
w = width(1.5)
|
||||
painter.fillRect(r.left(), r.top(), w, self.cell_height, cc)
|
||||
elif cs == 'underline':
|
||||
y = r.top() + self.font_metrics.underlinePos() + self.baseline_offset
|
||||
w = width(vert=False)
|
||||
painter.fillRect(r.left(), min(y, r.bottom() - w), self.cell_width, w, cc)
|
||||
else:
|
||||
painter.setPen(QPen(cc))
|
||||
painter.drawRect(r)
|
||||
254
kitty/term.py
254
kitty/term.py
@@ -2,47 +2,20 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from functools import lru_cache
|
||||
from itertools import product
|
||||
from collections import Counter
|
||||
from typing import Tuple, Iterator
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QTimer, QRect, Qt
|
||||
from PyQt5.QtGui import QColor, QPainter, QFont, QFontMetrics, QRegion, QPen, QPixmap
|
||||
from PyQt5.QtCore import pyqtSignal, QTimer, Qt
|
||||
from PyQt5.QtGui import QPainter
|
||||
from PyQt5.QtWidgets import QWidget, QApplication
|
||||
|
||||
from .config import build_ansi_color_tables, Options, fg_color_table, bg_color_table
|
||||
from .data_types import Cursor, COL_SHIFT, COL_MASK, as_color
|
||||
from .utils import set_current_font_metrics
|
||||
from .config import Options
|
||||
from .tracker import ChangeTracker
|
||||
from .screen import wrap_cursor_position
|
||||
from .keys import key_event_to_data
|
||||
from .screen import Screen
|
||||
from .render import Renderer
|
||||
from pyte.streams import Stream, DebugStream
|
||||
from pyte import modes as mo
|
||||
|
||||
|
||||
def ascii_width(fm: QFontMetrics) -> int:
|
||||
ans = 0
|
||||
for i in range(32, 128):
|
||||
ans = max(ans, fm.widthChar(chr(i)))
|
||||
return ans
|
||||
|
||||
|
||||
@lru_cache(maxsize=2**11)
|
||||
def pixmap_for_text(text, color, default_fg, font, w, h, baseline):
|
||||
p = QPixmap(w, h)
|
||||
p.fill(Qt.transparent)
|
||||
fg = as_color(color & COL_MASK, fg_color_table()) or default_fg
|
||||
painter = QPainter(p)
|
||||
painter.setFont(font)
|
||||
painter.setPen(QPen(QColor(*fg)))
|
||||
painter.setRenderHints(QPainter.TextAntialiasing | QPainter.Antialiasing)
|
||||
painter.drawText(0, baseline, text)
|
||||
painter.end()
|
||||
return p
|
||||
|
||||
|
||||
class TerminalWidget(QWidget):
|
||||
|
||||
relayout_lines = pyqtSignal(object, object)
|
||||
@@ -50,230 +23,53 @@ class TerminalWidget(QWidget):
|
||||
title_changed = pyqtSignal(object)
|
||||
icon_changed = pyqtSignal(object)
|
||||
send_data_to_child = pyqtSignal(object)
|
||||
cells_per_line = 80
|
||||
lines_per_screen = 24
|
||||
|
||||
def __init__(self, opts: Options, parent: QWidget=None, dump_commands: bool=False):
|
||||
QWidget.__init__(self, parent)
|
||||
self.setAttribute(Qt.WA_OpaquePaintEvent, True)
|
||||
self.setAutoFillBackground(False)
|
||||
self.cursor = Cursor()
|
||||
self.tracker = ChangeTracker(self)
|
||||
self.tracker.dirtied.connect(self.update_screen)
|
||||
sclass = DebugStream if dump_commands else Stream
|
||||
self.screen = Screen(opts, self.tracker, parent=self)
|
||||
for s in 'write_to_child title_changed icon_changed change_default_color'.split():
|
||||
getattr(self.screen, s).connect(getattr(self, s))
|
||||
self.stream = sclass(self.screen)
|
||||
self.feed = self.stream.feed
|
||||
self.last_drew_cursor_at = (0, 0)
|
||||
self.setFocusPolicy(Qt.WheelFocus)
|
||||
self.apply_opts(opts)
|
||||
self.debounce_resize_timer = t = QTimer(self)
|
||||
t.setSingleShot(True)
|
||||
t.setInterval(50)
|
||||
t.timeout.connect(self.do_layout)
|
||||
self.debounce_update_timer = t = QTimer(self)
|
||||
t.setSingleShot(True)
|
||||
t.setInterval(20)
|
||||
t.timeout.connect(self.do_update_screen)
|
||||
self.pending_update = QRegion()
|
||||
|
||||
self.tracker = ChangeTracker(self)
|
||||
sclass = DebugStream if dump_commands else Stream
|
||||
self.screen = Screen(opts, self.tracker, parent=self)
|
||||
for s in 'write_to_child title_changed icon_changed'.split():
|
||||
getattr(self.screen, s).connect(getattr(self, s))
|
||||
self.stream = sclass(self.screen)
|
||||
self.feed = self.stream.feed
|
||||
self.renderer = Renderer(self.screen, self.logicalDpiX(), self.logicalDpiY(), self)
|
||||
self.tracker.dirtied.connect(self.renderer.update_screen)
|
||||
self.renderer.update_required.connect(self.update_required)
|
||||
self.renderer.relayout_lines.connect(self.relayout_lines)
|
||||
self.apply_opts(opts)
|
||||
|
||||
def update_required(self):
|
||||
self.update()
|
||||
|
||||
def apply_opts(self, opts):
|
||||
self.screen.apply_opts(opts)
|
||||
self.opts = opts
|
||||
pixmap_for_text.cache_clear()
|
||||
pal = self.palette()
|
||||
pal.setColor(pal.Window, QColor(opts.background))
|
||||
pal.setColor(pal.WindowText, QColor(opts.foreground))
|
||||
self.setPalette(pal)
|
||||
self.default_bg = self.original_bg = pal.color(pal.Window)
|
||||
self.default_fg = self.original_fg = pal.color(pal.WindowText).getRgb()[:3]
|
||||
build_ansi_color_tables(opts)
|
||||
self.current_font = f = QFont(opts.font_family)
|
||||
f.setPointSizeF(opts.font_size)
|
||||
self.setFont(f)
|
||||
self.font_metrics = fm = QFontMetrics(self.font())
|
||||
self.cell_height = fm.lineSpacing()
|
||||
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.renderer.apply_opts(opts)
|
||||
self.do_layout()
|
||||
|
||||
def change_default_color(self, which, val):
|
||||
if which in ('fg', 'bg'):
|
||||
if not val:
|
||||
setattr(self, 'default_' + which, getattr(self, 'original_' + which))
|
||||
self.update()
|
||||
else:
|
||||
val = QColor(val)
|
||||
if val.isValid():
|
||||
if which == 'fg':
|
||||
self.default_fg = val.getRgb()[:3]
|
||||
else:
|
||||
self.default_bg = val
|
||||
self.update()
|
||||
|
||||
def do_layout(self):
|
||||
previous, self.cells_per_line = self.cells_per_line, self.width() // self.cell_width
|
||||
previousl, self.lines_per_screen = self.lines_per_screen, self.height() // self.cell_height
|
||||
self.hmargin = (self.width() - self.cells_per_line * self.cell_width) // 2
|
||||
self.vmargin = (self.height() % self.cell_height) // 2
|
||||
self.line_positions = tuple(self.vmargin + i * self.cell_height for i in range(self.lines_per_screen))
|
||||
self.cell_positions = tuple(self.hmargin + i * self.cell_width for i in range(self.cells_per_line))
|
||||
self.line_width = self.cells_per_line * self.cell_width
|
||||
self.layout_size = self.size()
|
||||
if (previous, previousl) != (self.cells_per_line, self.lines_per_screen):
|
||||
self.screen.resize(self.lines_per_screen, self.cells_per_line)
|
||||
self.relayout_lines.emit(self.cells_per_line, self.lines_per_screen)
|
||||
self.renderer.resize(self.size())
|
||||
self.update()
|
||||
|
||||
def resizeEvent(self, ev):
|
||||
self.debounce_resize_timer.start()
|
||||
|
||||
def update_screen(self, changes):
|
||||
self.cursor = changes['cursor'] or self.cursor
|
||||
|
||||
if changes['screen']:
|
||||
self.pending_update += self.rect()
|
||||
else:
|
||||
cell_positions, line_positions, cell_width, cell_height = self.cell_positions, self.line_positions, self.cell_width, self.cell_height
|
||||
old_x, old_y = self.last_drew_cursor_at
|
||||
rects = []
|
||||
for lnum in changes['lines']:
|
||||
try:
|
||||
rects.append(QRect(cell_positions[0], line_positions[lnum], self.line_width, cell_height))
|
||||
except IndexError:
|
||||
continue
|
||||
old_cursor_added = old_y in changes['lines']
|
||||
cursor_added = self.cursor.y in changes['lines']
|
||||
for lnum, ranges in changes['cells'].items():
|
||||
for start, stop in ranges:
|
||||
try:
|
||||
rects.append(QRect(cell_positions[start], line_positions[lnum], cell_width * (stop - start + 1), cell_height))
|
||||
except IndexError:
|
||||
continue
|
||||
if not old_cursor_added and old_y == lnum and (start <= old_x <= stop):
|
||||
old_cursor_added = True
|
||||
if not cursor_added and self.cursor.y == lnum and (start <= self.cursor.x <= stop):
|
||||
cursor_added = True
|
||||
rects.sort(key=lambda r: (r.y(), r.x()))
|
||||
for r in rects:
|
||||
self.pending_update += r
|
||||
if not cursor_added:
|
||||
try:
|
||||
self.pending_update += QRect(cell_positions[self.cursor.x], line_positions[self.cursor.y], cell_width, cell_height)
|
||||
except IndexError:
|
||||
pass
|
||||
if self.cursor.y == old_y and self.cursor.x == old_x:
|
||||
old_cursor_added = True
|
||||
if not old_cursor_added:
|
||||
try:
|
||||
self.pending_update += QRect(cell_positions[old_x], line_positions[old_y], cell_width, cell_height)
|
||||
except IndexError:
|
||||
pass
|
||||
if not self.debounce_update_timer.isActive():
|
||||
self.debounce_update_timer.start()
|
||||
|
||||
def do_update_screen(self):
|
||||
if not self.pending_update.isEmpty():
|
||||
self.update(self.pending_update)
|
||||
self.pending_update = QRegion()
|
||||
|
||||
def dirty_cells(self, region: QRegion) -> Iterator[Tuple[int]]:
|
||||
lines = (l for l in range(self.lines_per_screen) if region.intersects(QRect(
|
||||
self.hmargin, self.line_positions[l], self.cell_width * self.cells_per_line, self.cell_height)))
|
||||
cells = (c for c in range(self.cells_per_line) if region.intersects(QRect(
|
||||
self.cell_positions[c], self.vmargin, self.cell_width, self.cell_height * self.lines_per_screen)))
|
||||
return product(lines, cells)
|
||||
|
||||
def common_bg_color(self):
|
||||
c = Counter()
|
||||
for rdiv in range(1, 4):
|
||||
lnum = int(self.lines_per_screen * rdiv / 4)
|
||||
for cdiv in range(1, 4):
|
||||
cnum = int(self.cells_per_line * cdiv / 4)
|
||||
bgcol = self.screen.line(lnum).bgcolor(cnum)
|
||||
c[bgcol] += 1
|
||||
return c.most_common(1)[0][0]
|
||||
|
||||
def paintEvent(self, ev):
|
||||
if self.size() != self.layout_size:
|
||||
if self.size() != self.renderer.size():
|
||||
return
|
||||
r = ev.region()
|
||||
self.current_bgcol = self.common_bg_color()
|
||||
bg = self.default_bg
|
||||
if self.current_bgcol & 0xff:
|
||||
cbg = as_color(self.current_bgcol, bg_color_table())
|
||||
if cbg:
|
||||
bg = QColor(*cbg)
|
||||
|
||||
p = QPainter(self)
|
||||
p.fillRect(self.rect(), bg)
|
||||
|
||||
for lnum, cnum in self.dirty_cells(r):
|
||||
try:
|
||||
self.paint_cell(p, cnum, lnum)
|
||||
except Exception:
|
||||
pass
|
||||
if not self.cursor.hidden:
|
||||
x, y = wrap_cursor_position(self.cursor.x, self.cursor.y, len(self.line_positions), len(self.cell_positions))
|
||||
cr = QRect(self.cell_positions[x], self.line_positions[y], self.cell_width, self.cell_height)
|
||||
if r.intersects(cr):
|
||||
self.paint_cell(p, x, y, True)
|
||||
self.renderer.render(p)
|
||||
p.end()
|
||||
|
||||
def paint_cursor(self, painter, x, y):
|
||||
r = QRect(self.cell_positions[x], self.line_positions[y], self.cell_width, self.cell_height)
|
||||
self.last_drew_cursor_at = x, y
|
||||
cc = self.cursor_color
|
||||
if self.cursor.color:
|
||||
q = QColor(self.cursor.color)
|
||||
if q.isValid():
|
||||
cc = q
|
||||
cc.setAlphaF(self.opts.cursor_opacity)
|
||||
|
||||
def width(w=2, vert=True):
|
||||
dpi = self.logicalDpiX() if vert else self.logicalDpiY()
|
||||
return int(w * dpi / 72)
|
||||
|
||||
if self.hasFocus():
|
||||
cs = self.cursor.shape or self.opts.cursor_shape
|
||||
if cs == 'block':
|
||||
painter.fillRect(r, cc)
|
||||
elif cs == 'beam':
|
||||
w = width(1.5)
|
||||
painter.fillRect(r.left(), r.top(), w, self.cell_height, cc)
|
||||
elif cs == 'underline':
|
||||
y = r.top() + self.font_metrics.underlinePos() + self.baseline_offset
|
||||
w = width(vert=False)
|
||||
painter.fillRect(r.left(), min(y, r.bottom() - w), self.cell_width, w, cc)
|
||||
else:
|
||||
painter.setPen(QPen(cc))
|
||||
painter.drawRect(r)
|
||||
|
||||
def paint_cell(self, painter: QPainter, col: int, row: int, draw_cursor: bool=False) -> None:
|
||||
line = self.screen.line(row)
|
||||
ch, attrs, colors = line.basic_cell_data(col)
|
||||
x, y = self.cell_positions[col], self.line_positions[row]
|
||||
bgcol = colors >> COL_SHIFT
|
||||
if bgcol != self.current_bgcol:
|
||||
bg = as_color(colors >> COL_SHIFT, bg_color_table())
|
||||
if bg is not None:
|
||||
r = QRect(x, y, self.cell_width, self.cell_height)
|
||||
painter.fillRect(r, QColor(*bg))
|
||||
if draw_cursor:
|
||||
self.paint_cursor(painter, col, row)
|
||||
if ch == 0 or ch == 32:
|
||||
# An empty cell
|
||||
pass
|
||||
else:
|
||||
text = chr(ch) + line.combining_chars.get(col, '')
|
||||
p = pixmap_for_text(text, colors, self.default_fg, self.current_font, self.cell_width * 2, self.cell_height, self.baseline_offset)
|
||||
painter.drawPixmap(x, y, p)
|
||||
|
||||
def keyPressEvent(self, ev):
|
||||
mods = ev.modifiers()
|
||||
if mods & Qt.ControlModifier and mods & Qt.ShiftModifier:
|
||||
@@ -303,9 +99,11 @@ class TerminalWidget(QWidget):
|
||||
def focusInEvent(self, ev):
|
||||
if self.screen.enable_focus_tracking:
|
||||
self.send_data_to_child.emit(b'\x1b[I')
|
||||
self.renderer.set_has_focus(True)
|
||||
return QWidget.focusInEvent(self, ev)
|
||||
|
||||
def focusOutEvent(self, ev):
|
||||
if self.screen.enable_focus_tracking:
|
||||
self.send_data_to_child.emit(b'\x1b[O')
|
||||
self.renderer.set_has_focus(False)
|
||||
return QWidget.focusOutEvent(self, ev)
|
||||
|
||||
Reference in New Issue
Block a user