Line in icon tool
This commit is contained in:
@@ -4,12 +4,12 @@ import math
|
||||
from copy import deepcopy
|
||||
from typing import Protocol
|
||||
|
||||
from PySide6.QtCore import QObject, QPointF, QRectF, Qt, Signal
|
||||
from PySide6.QtCore import QLineF, QObject, QPointF, QRectF, Qt, Signal
|
||||
from PySide6.QtGui import QBrush, QColor, QFont, QPainter, QPainterPath, QPen
|
||||
from PySide6.QtWidgets import QGraphicsItem, QGraphicsPathItem, QGraphicsRectItem, QGraphicsScene, QGraphicsSceneContextMenuEvent, QGraphicsSceneMouseEvent, QMenu, QStyleOptionGraphicsItem, QWidget
|
||||
from PySide6.QtWidgets import QGraphicsItem, QGraphicsLineItem, QGraphicsPathItem, QGraphicsRectItem, QGraphicsScene, QGraphicsSceneContextMenuEvent, QGraphicsSceneMouseEvent, QMenu, QStyleOptionGraphicsItem, QWidget
|
||||
|
||||
from bedit_core.models import Port, PortID, SignalDirection
|
||||
from bedit_gui.models import Icon, LineType, Rectangle, Shape, ShapeID, Text
|
||||
from bedit_gui.models import Icon, Line, LineType, Rectangle, Shape, ShapeID, Text
|
||||
|
||||
|
||||
class ShapeCreationTool(Protocol):
|
||||
@@ -67,6 +67,45 @@ class TextCreationTool(RectangleCreationTool):
|
||||
return Text(layer=self.layer, pos=(round(rect.x()), round(rect.y())), width=rect.width(), height=rect.height(), text="Text")
|
||||
|
||||
|
||||
class LineCreationTool:
|
||||
def __init__(self, scene: QGraphicsScene, layer: int) -> None:
|
||||
self.scene = scene
|
||||
self.layer = layer
|
||||
self.start: QPointF | None = None
|
||||
self.preview: QGraphicsLineItem | None = None
|
||||
|
||||
def begin(self, position: QPointF) -> None:
|
||||
position = self._bounded(position)
|
||||
self.start = position
|
||||
self.preview = self.scene.addLine(QLineF(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.setLine(QLineF(self.start, self._bounded(position)))
|
||||
|
||||
def finish(self, position: QPointF) -> Shape | None:
|
||||
if self.start is None:
|
||||
return None
|
||||
start = self.start
|
||||
end = self._bounded(position)
|
||||
self.cancel()
|
||||
if start == end:
|
||||
return None
|
||||
return Line(layer=self.layer, pos=(round(start.x()), round(start.y())), end=(round(end.x()), round(end.y())))
|
||||
|
||||
def cancel(self) -> None:
|
||||
if self.preview is not None:
|
||||
self.scene.removeItem(self.preview)
|
||||
self.preview = None
|
||||
self.start = None
|
||||
|
||||
def _bounded(self, position: QPointF) -> QPointF:
|
||||
rect = self.scene.sceneRect()
|
||||
x = max(rect.left(), min(rect.right(), round(position.x())))
|
||||
y = max(rect.top(), min(rect.bottom(), round(position.y())))
|
||||
return QPointF(x, y)
|
||||
|
||||
|
||||
class ShapeGraphicsItem(QGraphicsPathItem):
|
||||
handle_size = 6.0
|
||||
|
||||
@@ -76,7 +115,7 @@ class ShapeGraphicsItem(QGraphicsPathItem):
|
||||
self.shape_model = deepcopy(shape)
|
||||
self.icon_scene = scene
|
||||
self._original_shape: Shape | None = None
|
||||
self._resizing = False
|
||||
self._resize_handle: str | None = None
|
||||
self.setFlags(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable | QGraphicsItem.GraphicsItemFlag.ItemIsMovable | QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges)
|
||||
|
||||
def resize_handle_rect(self) -> QRectF:
|
||||
@@ -84,29 +123,32 @@ class ShapeGraphicsItem(QGraphicsPathItem):
|
||||
corner = self.path().boundingRect().bottomRight()
|
||||
return QRectF(corner.x() - size / 2, corner.y() - size / 2, size, size)
|
||||
|
||||
def resize_handles(self) -> dict[str, QRectF]:
|
||||
return {"size": self.resize_handle_rect()}
|
||||
|
||||
def boundingRect(self) -> QRectF:
|
||||
margin = self.handle_size / 2
|
||||
return super().boundingRect().adjusted(-margin, -margin, margin, margin)
|
||||
|
||||
def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None:
|
||||
self._original_shape = self.current_shape()
|
||||
self._resizing = self.isSelected() and self.resize_handle_rect().contains(event.pos())
|
||||
if self._resizing:
|
||||
self._resize_handle = next((name for name, rect in self.resize_handles().items() if self.isSelected() and rect.contains(event.pos())), None)
|
||||
if self._resize_handle is not None:
|
||||
event.accept()
|
||||
return
|
||||
super().mousePressEvent(event)
|
||||
|
||||
def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None:
|
||||
if self._resizing:
|
||||
self.resize_to(event.scenePos())
|
||||
if self._resize_handle is not None:
|
||||
self.resize_to(event.scenePos(), self._resize_handle)
|
||||
event.accept()
|
||||
return
|
||||
super().mouseMoveEvent(event)
|
||||
|
||||
def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None:
|
||||
if self._resizing:
|
||||
self.resize_to(event.scenePos())
|
||||
self._resizing = False
|
||||
if self._resize_handle is not None:
|
||||
self.resize_to(event.scenePos(), self._resize_handle)
|
||||
self._resize_handle = None
|
||||
event.accept()
|
||||
else:
|
||||
super().mouseReleaseEvent(event)
|
||||
@@ -130,9 +172,9 @@ class ShapeGraphicsItem(QGraphicsPathItem):
|
||||
position = value
|
||||
if isinstance(position, QPointF):
|
||||
bounds = self.icon_scene.sceneRect()
|
||||
size = self.path().boundingRect()
|
||||
x = max(bounds.left(), min(bounds.right() - size.width(), round(position.x())))
|
||||
y = max(bounds.top(), min(bounds.bottom() - size.height(), round(position.y())))
|
||||
shape_rect = self.path().boundingRect()
|
||||
x = max(bounds.left() - shape_rect.left(), min(bounds.right() - shape_rect.right(), round(position.x())))
|
||||
y = max(bounds.top() - shape_rect.top(), min(bounds.bottom() - shape_rect.bottom(), round(position.y())))
|
||||
return QPointF(x, y)
|
||||
return super().itemChange(change, value)
|
||||
|
||||
@@ -141,12 +183,13 @@ class ShapeGraphicsItem(QGraphicsPathItem):
|
||||
if self.isSelected():
|
||||
painter.setPen(QPen(QColor("#ffffff")))
|
||||
painter.setBrush(QBrush(QColor("#2675bf")))
|
||||
painter.drawRect(self.resize_handle_rect())
|
||||
for rect in self.resize_handles().values():
|
||||
painter.drawRect(rect)
|
||||
|
||||
def current_shape(self) -> Shape:
|
||||
raise NotImplementedError
|
||||
|
||||
def resize_to(self, position: QPointF) -> None:
|
||||
def resize_to(self, position: QPointF, handle: str) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@@ -168,7 +211,7 @@ class RectangleGraphicsItem(ShapeGraphicsItem):
|
||||
shape.height = rect.height()
|
||||
return shape
|
||||
|
||||
def resize_to(self, position: QPointF) -> None:
|
||||
def resize_to(self, position: QPointF, _handle: str) -> None:
|
||||
bounds = self.icon_scene.sceneRect()
|
||||
width = max(1.0, min(bounds.right(), round(position.x())) - self.pos().x())
|
||||
height = max(1.0, min(bounds.bottom(), round(position.y())) - self.pos().y())
|
||||
@@ -199,7 +242,7 @@ class TextGraphicsItem(ShapeGraphicsItem):
|
||||
shape.height = rect.height()
|
||||
return shape
|
||||
|
||||
def resize_to(self, position: QPointF) -> None:
|
||||
def resize_to(self, position: QPointF, _handle: str) -> None:
|
||||
bounds = self.icon_scene.sceneRect()
|
||||
width = max(1.0, min(bounds.right(), round(position.x())) - self.pos().x())
|
||||
height = max(1.0, min(bounds.bottom(), round(position.y())) - self.pos().y())
|
||||
@@ -223,6 +266,45 @@ class TextGraphicsItem(ShapeGraphicsItem):
|
||||
self.setPath(path)
|
||||
|
||||
|
||||
class LineGraphicsItem(ShapeGraphicsItem):
|
||||
def __init__(self, shape_id: ShapeID, shape: Line, scene: IconGraphicsScene) -> None:
|
||||
super().__init__(shape_id, shape, scene)
|
||||
self.line = deepcopy(shape)
|
||||
self._set_points(QPointF(shape.pos[0], shape.pos[1]), QPointF(shape.end[0], shape.end[1]))
|
||||
self.setPen(scene._line_pen(shape.line_type, shape.line_thickness, shape.line_color))
|
||||
self.setZValue(shape.layer)
|
||||
|
||||
def resize_handles(self) -> dict[str, QRectF]:
|
||||
size = self.handle_size
|
||||
start = self.path().pointAtPercent(0)
|
||||
end = self.path().pointAtPercent(1)
|
||||
return {"start": QRectF(start.x() - size / 2, start.y() - size / 2, size, size), "end": QRectF(end.x() - size / 2, end.y() - size / 2, size, size)}
|
||||
|
||||
def current_shape(self) -> Line:
|
||||
shape = deepcopy(self.line)
|
||||
start = self.mapToScene(self.path().pointAtPercent(0))
|
||||
end = self.mapToScene(self.path().pointAtPercent(1))
|
||||
shape.pos = (round(start.x()), round(start.y()))
|
||||
shape.end = (round(end.x()), round(end.y()))
|
||||
return shape
|
||||
|
||||
def resize_to(self, position: QPointF, handle: str) -> None:
|
||||
bounds = self.icon_scene.sceneRect()
|
||||
position = QPointF(max(bounds.left(), min(bounds.right(), round(position.x()))), max(bounds.top(), min(bounds.bottom(), round(position.y()))))
|
||||
start = self.mapToScene(self.path().pointAtPercent(0))
|
||||
end = self.mapToScene(self.path().pointAtPercent(1))
|
||||
start = position if handle == "start" else start
|
||||
end = position if handle == "end" else end
|
||||
self.setPos(0, 0)
|
||||
self._set_points(start, end)
|
||||
|
||||
def _set_points(self, start: QPointF, end: QPointF) -> None:
|
||||
self.prepareGeometryChange()
|
||||
path = QPainterPath(start)
|
||||
path.lineTo(end)
|
||||
self.setPath(path)
|
||||
|
||||
|
||||
class PortGraphicsItem(QGraphicsRectItem):
|
||||
size = 16.0
|
||||
|
||||
@@ -358,13 +440,19 @@ class IconGraphicsScene(QGraphicsScene):
|
||||
self.addItem(RectangleGraphicsItem(shape_id, shape, self))
|
||||
elif isinstance(shape, Text):
|
||||
self.addItem(TextGraphicsItem(shape_id, shape, self))
|
||||
elif isinstance(shape, Line):
|
||||
self.addItem(LineGraphicsItem(shape_id, shape, self))
|
||||
|
||||
@staticmethod
|
||||
def _pen(shape: Rectangle) -> QPen:
|
||||
return IconGraphicsScene._line_pen(shape.line_type, shape.line_thickness, shape.line_color)
|
||||
|
||||
@staticmethod
|
||||
def _line_pen(line_type: LineType, thickness: float, color: str) -> 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:
|
||||
if line_type is LineType.NONE:
|
||||
return QPen(Qt.PenStyle.NoPen)
|
||||
return QPen(IconGraphicsScene._color(shape.line_color), shape.line_thickness, styles[shape.line_type])
|
||||
return QPen(IconGraphicsScene._color(color), thickness, styles[line_type])
|
||||
|
||||
@staticmethod
|
||||
def _color(value: str) -> QColor:
|
||||
|
||||
Reference in New Issue
Block a user