Start of the icon editor

This commit is contained in:
2026-07-27 13:08:15 +02:00
parent 713d094b08
commit 09a9e3f12f
11 changed files with 686 additions and 5 deletions

View File

@@ -0,0 +1,123 @@
from __future__ import annotations
from typing import Protocol
from PySide6.QtCore import QObject, QPointF, QRectF, Qt, Signal
from PySide6.QtGui import QBrush, QColor, QPainterPath, QPen
from PySide6.QtWidgets import QGraphicsPathItem, QGraphicsRectItem, QGraphicsScene, QGraphicsSceneMouseEvent
from bedit_gui.models import Icon, LineType, Rectangle, Shape
class ShapeCreationTool(Protocol):
def begin(self, position: QPointF) -> None: ...
def update(self, position: QPointF) -> None: ...
def finish(self, position: QPointF) -> Shape | None: ...
def cancel(self) -> None: ...
class RectangleCreationTool:
def __init__(self, scene: QGraphicsScene, layer: int) -> None:
self.scene = scene
self.layer = layer
self.start: QPointF | None = None
self.preview: QGraphicsRectItem | None = None
def begin(self, position: QPointF) -> None:
self.start = position
self.preview = self.scene.addRect(QRectF(position, position), QPen(Qt.PenStyle.DashLine))
def update(self, position: QPointF) -> None:
if self.preview is not None and self.start is not None:
self.preview.setRect(QRectF(self.start, position).normalized())
def finish(self, position: QPointF) -> Rectangle | None:
if self.start is None:
return None
rect = QRectF(self.start, position).normalized()
self.cancel()
if rect.width() < 1 or rect.height() < 1:
return None
return Rectangle(layer=self.layer, pos=(round(rect.x()), round(rect.y())), width=rect.width(), height=rect.height())
def cancel(self) -> None:
if self.preview is not None:
self.scene.removeItem(self.preview)
self.preview = None
self.start = None
class IconGraphicsScene(QGraphicsScene):
shape_created = Signal(object)
tool_active_changed = Signal(bool)
def __init__(self, parent: QObject | None = None) -> None:
super().__init__(parent)
self._tool: ShapeCreationTool | None = None
self.setSceneRect(-100, -100, 200, 200)
def set_icon(self, icon: Icon) -> None:
self.cancel_creation_tool()
self.clear()
for shape in sorted(icon.shapes.values(), key=lambda item: item.layer):
self._add_shape_item(shape)
def set_creation_tool(self, tool: ShapeCreationTool) -> None:
self.cancel_creation_tool()
self._tool = tool
self.tool_active_changed.emit(True)
def cancel_creation_tool(self) -> None:
if self._tool is None:
return
self._tool.cancel()
self._tool = None
self.tool_active_changed.emit(False)
def has_creation_tool(self) -> bool:
return self._tool is not None
def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None:
if self._tool is not None and event.button() == Qt.MouseButton.LeftButton:
self._tool.begin(event.scenePos())
event.accept()
return
super().mousePressEvent(event)
def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None:
if self._tool is not None and event.buttons() & Qt.MouseButton.LeftButton:
self._tool.update(event.scenePos())
event.accept()
return
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None:
if self._tool is not None and event.button() == Qt.MouseButton.LeftButton:
tool = self._tool
shape = tool.finish(event.scenePos())
self._tool = None
self.tool_active_changed.emit(False)
if shape is not None:
self.shape_created.emit(shape)
event.accept()
return
super().mouseReleaseEvent(event)
def _add_shape_item(self, shape: Shape) -> None:
if not isinstance(shape, Rectangle):
return
rect = QRectF(shape.pos[0], shape.pos[1], shape.width, shape.height)
path = QPainterPath()
path.addRoundedRect(rect, shape.corner_radius, shape.corner_radius)
item = QGraphicsPathItem(path)
item.setPen(self._pen(shape))
item.setBrush(QBrush(QColor(shape.fill_color)))
item.setZValue(shape.layer)
self.addItem(item)
@staticmethod
def _pen(shape: Rectangle) -> QPen:
styles = {LineType.SOLID: Qt.PenStyle.SolidLine, LineType.DASHED: Qt.PenStyle.DashLine, LineType.DOTTED: Qt.PenStyle.DotLine, LineType.DASH_DOT: Qt.PenStyle.DashDotLine}
if shape.line_type is LineType.NONE:
return QPen(Qt.PenStyle.NoPen)
return QPen(QColor(shape.line_color), shape.line_thickness, styles[shape.line_type])