Start with signal lib
This commit is contained in:
@@ -35,6 +35,7 @@ class LibraryController(QObject):
|
||||
tree.header().setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
|
||||
tree.setColumnWidth(1, 56)
|
||||
|
||||
window.ui.actionReload_Libraries.triggered.connect(self.reload)
|
||||
self.reload()
|
||||
|
||||
def reload(self) -> None:
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
<addaction name="actionDelete"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionSettings"/>
|
||||
<addaction name="actionReload_Libraries"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuView">
|
||||
<property name="title">
|
||||
@@ -351,6 +352,17 @@
|
||||
<string>Settings</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionReload_Libraries">
|
||||
<property name="text">
|
||||
<string>Reload Libraries</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Reload configured libraries</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDelete">
|
||||
<property name="icon">
|
||||
<iconset resource="../../resources/resources.qrc">
|
||||
|
||||
@@ -83,6 +83,9 @@ class Ui_MainWindow(object):
|
||||
self.actionToolbars.setObjectName(u"actionToolbars")
|
||||
self.actionSettings = QAction(MainWindow)
|
||||
self.actionSettings.setObjectName(u"actionSettings")
|
||||
self.actionReload_Libraries = QAction(MainWindow)
|
||||
self.actionReload_Libraries.setObjectName(u"actionReload_Libraries")
|
||||
self.actionReload_Libraries.setMenuRole(QAction.MenuRole.NoRole)
|
||||
self.actionDelete = QAction(MainWindow)
|
||||
self.actionDelete.setObjectName(u"actionDelete")
|
||||
icon7 = QIcon()
|
||||
@@ -228,6 +231,7 @@ class Ui_MainWindow(object):
|
||||
self.menuEdit.addAction(self.actionDelete)
|
||||
self.menuEdit.addSeparator()
|
||||
self.menuEdit.addAction(self.actionSettings)
|
||||
self.menuEdit.addAction(self.actionReload_Libraries)
|
||||
self.menuView.addAction(self.actionReset_Layout)
|
||||
self.menuView.addAction(self.actionPanels)
|
||||
self.menuView.addAction(self.actionToolbars)
|
||||
@@ -310,6 +314,10 @@ class Ui_MainWindow(object):
|
||||
self.actionPanels.setText(QCoreApplication.translate("MainWindow", u"Panels", None))
|
||||
self.actionToolbars.setText(QCoreApplication.translate("MainWindow", u"Toolbars", None))
|
||||
self.actionSettings.setText(QCoreApplication.translate("MainWindow", u"Settings", None))
|
||||
self.actionReload_Libraries.setText(QCoreApplication.translate("MainWindow", u"Reload Libraries", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.actionReload_Libraries.setToolTip(QCoreApplication.translate("MainWindow", u"Reload configured libraries", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.actionDelete.setText(QCoreApplication.translate("MainWindow", u"Delete", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.actionDelete.setToolTip(QCoreApplication.translate("MainWindow", u"Delete selected", None))
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from math import ceil
|
||||
|
||||
from PySide6.QtCore import QRectF, QSize, Qt
|
||||
from PySide6.QtGui import QBrush, QColor, QFont, QIcon, QPainter, QPen, QPixmap, QRegion
|
||||
|
||||
@@ -8,6 +10,7 @@ from bedit_gui.models import Icon, Line, LineType, Rectangle, Text
|
||||
|
||||
PORT_SIZE = 16
|
||||
DEFAULT_ICON_SIZE = QSize(48, 48)
|
||||
EMPTY_NATURAL_ICON_SIZE = QSize(32, 32)
|
||||
ICON_MARGIN = 4
|
||||
ICON_PREVIEW_OVERSAMPLE = 4
|
||||
|
||||
@@ -43,6 +46,13 @@ def get_pixmap_bounding_box(pixmap: QPixmap) -> QRectF:
|
||||
return QRectF(bounds) if not bounds.isEmpty() else QRectF(0, 0, pixmap.width(), pixmap.height())
|
||||
|
||||
|
||||
def get_natural_icon_size(icon: Icon) -> QSize:
|
||||
bounds = get_bounding_box(icon)
|
||||
if bounds.isEmpty():
|
||||
return QSize(EMPTY_NATURAL_ICON_SIZE)
|
||||
return QSize(max(1, ceil(bounds.width()) + ICON_MARGIN), max(1, ceil(bounds.height()) + ICON_MARGIN))
|
||||
|
||||
|
||||
def render_icon(icon: Icon, ports: dict[PortID, Port], size: QSize = DEFAULT_ICON_SIZE, render_ports: bool = False) -> QIcon:
|
||||
pixmap = QPixmap(size)
|
||||
pixmap.fill(Qt.GlobalColor.transparent)
|
||||
|
||||
@@ -5,7 +5,7 @@ from enum import Enum
|
||||
from itertools import pairwise
|
||||
from math import hypot
|
||||
|
||||
from PySide6.QtCore import QEvent, QObject, QPointF, QRectF, QSize, QTimer, Qt, Signal
|
||||
from PySide6.QtCore import QEvent, QObject, QPointF, QRectF, QTimer, Qt, Signal
|
||||
from PySide6.QtGui import QActionGroup, QBrush, QColor, QCursor, QDragEnterEvent, QDragMoveEvent, QDropEvent, QKeySequence, QMouseEvent, QPainter, QPainterPath, QPen, QShortcut, QWheelEvent
|
||||
from PySide6.QtWidgets import QGraphicsEllipseItem, QGraphicsItem, QGraphicsPathItem, QGraphicsPixmapItem, QGraphicsScene, QGraphicsSceneMouseEvent, QGraphicsTextItem, QGraphicsView, QMenu, QWidget
|
||||
|
||||
@@ -13,7 +13,7 @@ from bedit_core.models import BondCausality, BondConnection, BondPort, Component
|
||||
from bedit_gui.models import Graph, GraphComponentLabel, GraphConnection, Icon
|
||||
from bedit_gui.services.component_clipboard import COMPONENTS_MIME
|
||||
from bedit_gui.ui.generated.ui_graph_editor_widget import Ui_graphEditorWidget
|
||||
from bedit_gui.utils.icon import get_pixmap_bounding_box, render_icon
|
||||
from bedit_gui.utils.icon import get_natural_icon_size, get_pixmap_bounding_box, render_icon
|
||||
|
||||
GRID_SPACING = 64
|
||||
SCENE_SIZE = 10000
|
||||
@@ -21,14 +21,18 @@ MIN_ZOOM = 0.2
|
||||
MAX_ZOOM = 4.0
|
||||
ZOOM_STEP = 1.15
|
||||
ZOOM_TO_FIT_PADDING = 32.0
|
||||
COMPONENT_ICON_SIZE = QSize(128, 128)
|
||||
COMPONENT_LABEL_FONT_SIZE = 24.0
|
||||
FALLBACK_COMPONENT_SPACING = 128
|
||||
CONNECTION_WIDTH = 4.0
|
||||
CONNECTION_BOUNDING_BOX_SPACING = 16.0
|
||||
BOND_CONNECTION_COLOR = "#000000"
|
||||
SIGNAL_CONNECTION_COLOR = "#00007f"
|
||||
BOND_CONNECTION_BOUNDING_BOX_SPACING = 16.0
|
||||
SIGNAL_CONNECTION_BOUNDING_BOX_SPACING = 0.0
|
||||
CONNECTION_STRAIGHTEN_TOLERANCE = 8.0
|
||||
ARROW_LENGTH = 32.0
|
||||
ARROW_HALF_WIDTH = 16.0
|
||||
SIGNAL_ARROW_LENGTH = ARROW_LENGTH / 2
|
||||
SIGNAL_ARROW_HALF_WIDTH = ARROW_HALF_WIDTH / 2
|
||||
CAUSALITY_TICK_HALF_LENGTH = 16.0
|
||||
|
||||
|
||||
@@ -66,8 +70,10 @@ class GraphConnectionItem(QGraphicsPathItem):
|
||||
self.connection_id = connection_id
|
||||
self.editor = editor
|
||||
self.setPath(self._connection_path(points, half_arrow, tick_at_source))
|
||||
pen = QPen(QColor("#202020"), CONNECTION_WIDTH)
|
||||
color = QColor(BOND_CONNECTION_COLOR if half_arrow else SIGNAL_CONNECTION_COLOR)
|
||||
pen = QPen(color, CONNECTION_WIDTH)
|
||||
self.setPen(pen)
|
||||
self.setBrush(QBrush(color))
|
||||
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable)
|
||||
self.setZValue(-1)
|
||||
|
||||
@@ -100,15 +106,17 @@ class GraphConnectionItem(QGraphicsPathItem):
|
||||
dx = target.x() - previous.x()
|
||||
dy = target.y() - previous.y()
|
||||
length = hypot(dx, dy)
|
||||
back_x = target.x() - ARROW_LENGTH * dx / length
|
||||
back_y = target.y() - ARROW_LENGTH * dy / length
|
||||
perpendicular_x = -ARROW_HALF_WIDTH * dy / length
|
||||
perpendicular_y = ARROW_HALF_WIDTH * dx / length
|
||||
arrow_length = ARROW_LENGTH if half_arrow else SIGNAL_ARROW_LENGTH
|
||||
arrow_half_width = ARROW_HALF_WIDTH if half_arrow else SIGNAL_ARROW_HALF_WIDTH
|
||||
back_x = target.x() - arrow_length * dx / length
|
||||
back_y = target.y() - arrow_length * dy / length
|
||||
perpendicular_x = -arrow_half_width * dy / length
|
||||
perpendicular_y = arrow_half_width * dx / length
|
||||
path.moveTo(target)
|
||||
path.lineTo(back_x + perpendicular_x, back_y + perpendicular_y)
|
||||
if not half_arrow:
|
||||
path.moveTo(target)
|
||||
path.lineTo(back_x - perpendicular_x, back_y - perpendicular_y)
|
||||
path.closeSubpath()
|
||||
if tick_at_source is not None:
|
||||
GraphConnectionItem._add_causality_tick(path, points, tick_at_source)
|
||||
return path
|
||||
@@ -315,6 +323,7 @@ class GraphEditorWidget(QWidget):
|
||||
self.scene = GraphGraphicsScene(self)
|
||||
self.scene.setSceneRect(-SCENE_SIZE / 2, -SCENE_SIZE / 2, SCENE_SIZE, SCENE_SIZE)
|
||||
self.ui.graphicsView.setScene(self.scene)
|
||||
self.ui.graphicsView.setRenderHint(QPainter.RenderHint.Antialiasing)
|
||||
self.ui.graphicsView.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse)
|
||||
self.ui.graphicsView.viewport().installEventFilter(self)
|
||||
self.ui.graphicsView.viewport().setAcceptDrops(True)
|
||||
@@ -381,7 +390,8 @@ class GraphEditorWidget(QWidget):
|
||||
positions = {component_id: graph.component_positions.get(component_id, (index * FALLBACK_COMPONENT_SPACING, 0)) for index, component_id in enumerate(component.implementation.graph.components)}
|
||||
for component_id, child in component.implementation.graph.components.items():
|
||||
icon = icons.get(component_id, Icon())
|
||||
pixmap = render_icon(icon, child.interface.ports, COMPONENT_ICON_SIZE, render_ports=self._mode is GraphEditorMode.CONNECTION).pixmap(COMPONENT_ICON_SIZE)
|
||||
icon_size = get_natural_icon_size(icon)
|
||||
pixmap = render_icon(icon, child.interface.ports, icon_size, render_ports=self._mode is GraphEditorMode.CONNECTION).pixmap(icon_size)
|
||||
item = GraphComponentItem(component_id, pixmap, self)
|
||||
item.setOffset(-pixmap.width() / 2, -pixmap.height() / 2)
|
||||
item.setPos(*positions[component_id])
|
||||
@@ -435,7 +445,8 @@ class GraphEditorWidget(QWidget):
|
||||
points = self._straighten_direct_connection(points)
|
||||
source_bounds = self._component_bounds[source_component].translated(*source_position)
|
||||
target_bounds = self._component_bounds[target_component].translated(*target_position)
|
||||
points = self._clip_connection(points, source_bounds, target_bounds)
|
||||
spacing = BOND_CONNECTION_BOUNDING_BOX_SPACING if isinstance(connection, BondConnection) else SIGNAL_CONNECTION_BOUNDING_BOX_SPACING
|
||||
points = self._clip_connection(points, source_bounds, target_bounds, spacing)
|
||||
tick_at_source = None
|
||||
if isinstance(connection, BondConnection):
|
||||
tick_at_source = False if connection.causality is BondCausality.EFFORT_OUT else True if connection.causality is BondCausality.FLOW_OUT else None
|
||||
@@ -582,7 +593,7 @@ class GraphEditorWidget(QWidget):
|
||||
return points
|
||||
|
||||
@staticmethod
|
||||
def _clip_connection(points: list[tuple[float, float]], source_bounds: QRectF, target_bounds: QRectF) -> list[tuple[float, float]]:
|
||||
def _clip_connection(points: list[tuple[float, float]], source_bounds: QRectF, target_bounds: QRectF, spacing: float) -> list[tuple[float, float]]:
|
||||
source = points[0]
|
||||
target = points[-1]
|
||||
source_direction = next((point for point in points[1:] if point != source), None)
|
||||
@@ -590,13 +601,13 @@ class GraphEditorWidget(QWidget):
|
||||
if source_direction is None or target_direction is None:
|
||||
return points
|
||||
clipped = list(points)
|
||||
clipped[0] = GraphEditorWidget._bounding_box_edge(source_bounds, source, source_direction)
|
||||
clipped[-1] = GraphEditorWidget._bounding_box_edge(target_bounds, target, target_direction)
|
||||
clipped[0] = GraphEditorWidget._bounding_box_edge(source_bounds, source, source_direction, spacing)
|
||||
clipped[-1] = GraphEditorWidget._bounding_box_edge(target_bounds, target, target_direction, spacing)
|
||||
return clipped
|
||||
|
||||
@staticmethod
|
||||
def _bounding_box_edge(bounds: QRectF, origin: tuple[float, float], toward: tuple[float, float]) -> tuple[float, float]:
|
||||
bounds = bounds.adjusted(-CONNECTION_BOUNDING_BOX_SPACING, -CONNECTION_BOUNDING_BOX_SPACING, CONNECTION_BOUNDING_BOX_SPACING, CONNECTION_BOUNDING_BOX_SPACING)
|
||||
def _bounding_box_edge(bounds: QRectF, origin: tuple[float, float], toward: tuple[float, float], spacing: float) -> tuple[float, float]:
|
||||
bounds = bounds.adjusted(-spacing, -spacing, spacing, spacing)
|
||||
dx = toward[0] - origin[0]
|
||||
dy = toward[1] - origin[1]
|
||||
horizontal_scale = (bounds.right() - origin[0]) / dx if dx > 0 else (bounds.left() - origin[0]) / dx if dx < 0 else float("inf")
|
||||
@@ -664,7 +675,8 @@ class GraphEditorWidget(QWidget):
|
||||
self._connection_preview.setPath(QPainterPath(mouse_position))
|
||||
return
|
||||
bounds = self._component_bounds[self._connection_start].translated(*source)
|
||||
start = self._bounding_box_edge(bounds, source, (mouse_position.x(), mouse_position.y()))
|
||||
spacing = max(BOND_CONNECTION_BOUNDING_BOX_SPACING, SIGNAL_CONNECTION_BOUNDING_BOX_SPACING)
|
||||
start = self._bounding_box_edge(bounds, source, (mouse_position.x(), mouse_position.y()), spacing)
|
||||
path = QPainterPath(QPointF(*start))
|
||||
path.lineTo(mouse_position)
|
||||
self._connection_preview.setPath(path)
|
||||
|
||||
@@ -11,6 +11,8 @@ from PySide6.QtWidgets import QGraphicsItem, QGraphicsLineItem, QGraphicsPathIte
|
||||
from bedit_core.models import Port, PortID, SignalDirection
|
||||
from bedit_gui.models import Icon, Line, LineType, Rectangle, Shape, ShapeID, Text
|
||||
|
||||
ICON_SCENE_SIZE = 512
|
||||
|
||||
|
||||
class ShapeCreationTool(Protocol):
|
||||
def begin(self, position: QPointF) -> None: ...
|
||||
@@ -356,7 +358,7 @@ class IconGraphicsScene(QGraphicsScene):
|
||||
super().__init__(parent)
|
||||
self._tool: ShapeCreationTool | None = None
|
||||
self._ports: dict[PortID, Port] = {}
|
||||
self.setSceneRect(-64, -64, 128, 128)
|
||||
self.setSceneRect(-ICON_SCENE_SIZE / 2, -ICON_SCENE_SIZE / 2, ICON_SCENE_SIZE, ICON_SCENE_SIZE)
|
||||
|
||||
def set_ports(self, ports: dict[PortID, Port]) -> None:
|
||||
self._ports = deepcopy(ports)
|
||||
|
||||
Reference in New Issue
Block a user