vibefpmgui.console_view
The Output panel's text: fpm's build progress ("[ 25%]" at a line's start) drawn as a rounded badge, "25%", its color going from blue at 0% to green at 100%. Drawn over the text, which stays as fpm wrote it (so it's selected and copied as such).
1"""The Output panel's text: fpm's build progress ("[ 25%]" at a line's 2start) drawn as a rounded badge, "25%", its color going from blue at 0% 3to green at 100%. Drawn over the text, which stays as fpm wrote it (so 4it's selected and copied as such). 5""" 6 7from __future__ import annotations 8 9import re 10 11from qtpy.QtCore import QRectF, Qt 12from qtpy.QtGui import QColor, QFont, QPainter, QTextCursor 13from qtpy.QtWidgets import QPlainTextEdit 14 15PROGRESS_RE = re.compile(r"^\[\s*(\d{1,3})%\]") 16 17 18def progress_color(percent: int) -> QColor: 19 """Blue at 0%, green at 100% (through teal), bright enough for white text.""" 20 fraction = max(0, min(100, percent)) / 100 21 return QColor.fromHsvF(0.60 - 0.24 * fraction, 0.70, 0.78) 22 23 24class ConsoleView(QPlainTextEdit): 25 """A read-only text view for command output, with fpm's progress as badges.""" 26 27 def progress_badges(self) -> list[tuple[int, QRectF]]: 28 """(percent, where its badge is drawn) for the lines on view.""" 29 out = [] 30 block = self.firstVisibleBlock() 31 offset = self.contentOffset() 32 height = self.viewport().height() 33 while block.isValid(): 34 geometry = self.blockBoundingGeometry(block).translated(offset) 35 if geometry.top() > height: 36 break 37 match = PROGRESS_RE.match(block.text()) 38 if match and block.isVisible(): 39 start, end = QTextCursor(block), QTextCursor(block) 40 start.setPosition(block.position() + match.start()) 41 end.setPosition(block.position() + match.end()) 42 left, right = self.cursorRect(start), self.cursorRect(end) 43 if left.top() == right.top(): # (not wrapped in the middle) 44 out.append((int(match.group(1)), QRectF(left.left(), left.top() + 1, right.left() - left.left(), 45 left.height() - 2))) 46 block = block.next() 47 return out 48 49 def paintEvent(self, event): 50 super().paintEvent(event) 51 badges = self.progress_badges() 52 if not badges: 53 return 54 painter = QPainter(self.viewport()) 55 painter.setRenderHint(QPainter.Antialiasing) 56 font = QFont(self.font()) 57 font.setBold(True) 58 font.setPointSizeF(max(6.0, font.pointSizeF() * 0.85)) 59 painter.setFont(font) 60 for percent, rect in badges: 61 painter.setPen(Qt.NoPen) 62 painter.setBrush(progress_color(percent)) 63 painter.drawRoundedRect(rect, rect.height() / 2, rect.height() / 2) 64 painter.setPen(QColor("white")) 65 painter.drawText(rect, Qt.AlignCenter, f"{percent}%") 66 painter.end()
PROGRESS_RE =
re.compile('^\\[\\s*(\\d{1,3})%\\]')
def
progress_color(percent: int) -> PySide6.QtGui.QColor:
19def progress_color(percent: int) -> QColor: 20 """Blue at 0%, green at 100% (through teal), bright enough for white text.""" 21 fraction = max(0, min(100, percent)) / 100 22 return QColor.fromHsvF(0.60 - 0.24 * fraction, 0.70, 0.78)
Blue at 0%, green at 100% (through teal), bright enough for white text.
class
ConsoleView(PySide6.QtWidgets.QPlainTextEdit):
25class ConsoleView(QPlainTextEdit): 26 """A read-only text view for command output, with fpm's progress as badges.""" 27 28 def progress_badges(self) -> list[tuple[int, QRectF]]: 29 """(percent, where its badge is drawn) for the lines on view.""" 30 out = [] 31 block = self.firstVisibleBlock() 32 offset = self.contentOffset() 33 height = self.viewport().height() 34 while block.isValid(): 35 geometry = self.blockBoundingGeometry(block).translated(offset) 36 if geometry.top() > height: 37 break 38 match = PROGRESS_RE.match(block.text()) 39 if match and block.isVisible(): 40 start, end = QTextCursor(block), QTextCursor(block) 41 start.setPosition(block.position() + match.start()) 42 end.setPosition(block.position() + match.end()) 43 left, right = self.cursorRect(start), self.cursorRect(end) 44 if left.top() == right.top(): # (not wrapped in the middle) 45 out.append((int(match.group(1)), QRectF(left.left(), left.top() + 1, right.left() - left.left(), 46 left.height() - 2))) 47 block = block.next() 48 return out 49 50 def paintEvent(self, event): 51 super().paintEvent(event) 52 badges = self.progress_badges() 53 if not badges: 54 return 55 painter = QPainter(self.viewport()) 56 painter.setRenderHint(QPainter.Antialiasing) 57 font = QFont(self.font()) 58 font.setBold(True) 59 font.setPointSizeF(max(6.0, font.pointSizeF() * 0.85)) 60 painter.setFont(font) 61 for percent, rect in badges: 62 painter.setPen(Qt.NoPen) 63 painter.setBrush(progress_color(percent)) 64 painter.drawRoundedRect(rect, rect.height() / 2, rect.height() / 2) 65 painter.setPen(QColor("white")) 66 painter.drawText(rect, Qt.AlignCenter, f"{percent}%") 67 painter.end()
A read-only text view for command output, with fpm's progress as badges.
def
progress_badges(self) -> list[tuple[int, PySide6.QtCore.QRectF]]:
28 def progress_badges(self) -> list[tuple[int, QRectF]]: 29 """(percent, where its badge is drawn) for the lines on view.""" 30 out = [] 31 block = self.firstVisibleBlock() 32 offset = self.contentOffset() 33 height = self.viewport().height() 34 while block.isValid(): 35 geometry = self.blockBoundingGeometry(block).translated(offset) 36 if geometry.top() > height: 37 break 38 match = PROGRESS_RE.match(block.text()) 39 if match and block.isVisible(): 40 start, end = QTextCursor(block), QTextCursor(block) 41 start.setPosition(block.position() + match.start()) 42 end.setPosition(block.position() + match.end()) 43 left, right = self.cursorRect(start), self.cursorRect(end) 44 if left.top() == right.top(): # (not wrapped in the middle) 45 out.append((int(match.group(1)), QRectF(left.left(), left.top() + 1, right.left() - left.left(), 46 left.height() - 2))) 47 block = block.next() 48 return out
(percent, where its badge is drawn) for the lines on view.
def
paintEvent(self, event):
50 def paintEvent(self, event): 51 super().paintEvent(event) 52 badges = self.progress_badges() 53 if not badges: 54 return 55 painter = QPainter(self.viewport()) 56 painter.setRenderHint(QPainter.Antialiasing) 57 font = QFont(self.font()) 58 font.setBold(True) 59 font.setPointSizeF(max(6.0, font.pointSizeF() * 0.85)) 60 painter.setFont(font) 61 for percent, rect in badges: 62 painter.setPen(Qt.NoPen) 63 painter.setBrush(progress_color(percent)) 64 painter.drawRoundedRect(rect, rect.height() / 2, rect.height() / 2) 65 painter.setPen(QColor("white")) 66 painter.drawText(rect, Qt.AlignCenter, f"{percent}%") 67 painter.end()
paintEvent(self, e: PySide6.QtGui.QPaintEvent, /) -> None