107 lines
3.7 KiB
Python
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))
|