Files
BondGraph/BEdit/src/bedit/gui/graphics/icon_renderer.py

107 lines
3.7 KiB
Python

from PySide6.QtCore import QPointF, QRectF, Qt
from PySide6.QtGui import QColor, QFont, QIcon, QPainter, QPen, QPixmap, QPolygonF
from bedit.core.model import Icon
def icon_bounds(icon: Icon) -> QRectF:
"""Return the automatic hitbox of all visible vector elements."""
bounds = QRectF()
for element in icon.elements:
if element.get("_deleted"):
continue
rect = QRectF(
float(element.get("x", 0)),
float(element.get("y", 0)),
float(element.get("width", 0)),
float(element.get("height", 0)),
).normalized()
bounds = rect if bounds.isNull() else bounds.united(rect)
return bounds if not bounds.isNull() else QRectF(32, 32, 64, 64)
def shape_pen(element: dict) -> QPen:
styles = {
"solid": Qt.PenStyle.SolidLine,
"dash": Qt.PenStyle.DashLine,
"dot": Qt.PenStyle.DotLine,
"dash-dot": Qt.PenStyle.DashDotLine,
"none": Qt.PenStyle.NoPen,
}
return QPen(
QColor(element.get("stroke", "#303030")),
float(element.get("lineWidth", 1.5)),
styles.get(element.get("lineStyle", "solid"), Qt.PenStyle.SolidLine),
)
def paint_icon(
painter: QPainter,
icon: Icon,
target: QRectF,
source: QRectF | None = None,
) -> None:
painter.save()
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
if source is None:
source = QRectF(0, 0, icon.width, icon.height)
painter.translate(target.topLeft())
painter.scale(target.width() / source.width(), target.height() / source.height())
painter.translate(-source.left(), -source.top())
for element in icon.elements:
kind = element.get("type", "rectangle")
rect = QRectF(
float(element.get("x", 0)),
float(element.get("y", 0)),
float(element.get("width", 20)),
float(element.get("height", 20)),
)
painter.setPen(shape_pen(element))
fill = element.get("fill", "#ffffff")
painter.setBrush(
Qt.BrushStyle.NoBrush if fill in {"none", "transparent", ""} else QColor(fill)
)
if kind == "rectangle":
radius = float(element.get("cornerRadius", 0))
painter.drawRoundedRect(rect, radius, radius)
elif kind in {"circle", "ellipse"}:
painter.drawEllipse(rect)
elif kind == "line":
painter.drawLine(rect.topLeft(), rect.bottomRight())
elif kind == "triangle":
painter.drawPolygon(
QPolygonF(
[QPointF(rect.center().x(), rect.top()), rect.bottomRight(), rect.bottomLeft()]
)
)
elif kind == "text":
painter.setPen(QColor(element.get("color", element.get("stroke", "#202020"))))
font = QFont()
font.setPointSizeF(float(element.get("fontSize", 12)))
painter.setFont(font)
painter.drawText(rect, Qt.AlignmentFlag.AlignCenter, str(element.get("text", "Text")))
painter.restore()
def icon_pixmap(icon: Icon, size: int = 16) -> QPixmap:
pixmap = QPixmap(size, size)
pixmap.fill(Qt.GlobalColor.transparent)
painter = QPainter(pixmap)
bounds = icon_bounds(icon)
ratio = min(size / bounds.width(), size / bounds.height())
width, height = bounds.width() * ratio, bounds.height() * ratio
paint_icon(
painter,
icon,
QRectF((size - width) / 2, (size - height) / 2, width, height),
bounds,
)
painter.end()
return pixmap
def library_icon(icon: Icon) -> QIcon:
# Render large enough for the tall Libraries rows. icon_pixmap crops to the
# vector hitbox first, so a 32x32 drawing is shown as large as a 128x128 one.
return QIcon(icon_pixmap(icon, 28))