From 8df5c2a8f9f1c50e6455c8f8223210f71fa9abf3 Mon Sep 17 00:00:00 2001 From: Joppe Blondel Date: Sun, 19 Jul 2026 22:16:37 +0200 Subject: [PATCH] fix some things --- BEdit/README.md | 4 +- BEdit/data/libraries/test.bedit.json | 162 +++++++++++++++++++++++++++ BEdit/src/bedit/document/model.py | 6 +- BEdit/src/bedit/icon_renderer.py | 44 +++++++- BEdit/src/bedit/main_window.py | 8 +- BEdit/src/bedit/workspace/view.py | 46 +++++--- BEdit/test.bedit.json | 162 +++++++++++++++++++++++++++ 7 files changed, 404 insertions(+), 28 deletions(-) create mode 100644 BEdit/data/libraries/test.bedit.json create mode 100644 BEdit/test.bedit.json diff --git a/BEdit/README.md b/BEdit/README.md index 1509bcb..38af704 100644 --- a/BEdit/README.md +++ b/BEdit/README.md @@ -144,8 +144,8 @@ Every component owns its ports, declarative icon, properties, and child graph: "icon": { "size": {"width": 128, "height": 128}, "elements": [{ - "type": "rectangle", "x": 1, "y": 1, - "width": 126, "height": 126, + "type": "rectangle", "x": 32, "y": 32, + "width": 64, "height": 64, "cornerRadius": 5, "fill": "#dbeafe", "stroke": "#245c9c", "lineWidth": 1.5, "lineStyle": "solid" diff --git a/BEdit/data/libraries/test.bedit.json b/BEdit/data/libraries/test.bedit.json new file mode 100644 index 0000000..a79b1f5 --- /dev/null +++ b/BEdit/data/libraries/test.bedit.json @@ -0,0 +1,162 @@ +{ + "format": "bedit-document", + "version": 1, + "metadata": { + "name": "Untitled" + }, + "roots": [ + { + "id": "5ee742db-b25c-4d86-b4ca-cc0e61c4002a", + "name": "New Graph Block 1", + "position": { + "x": 0.0, + "y": 0.0 + }, + "rotation": 0.0, + "interface": { + "inputs": [], + "outputs": [] + }, + "icon": { + "shape": "rectangle", + "fill": "#f4f4f4", + "border": "#303030", + "text": "Graph", + "size": { + "width": 128.0, + "height": 128.0 + }, + "elements": [ + { + "type": "rectangle", + "x": 1.0, + "y": 1.0, + "width": 126.0, + "height": 126.0, + "fill": "#f4f4f4", + "stroke": "#303030", + "lineWidth": 1.5, + "lineStyle": "solid", + "cornerRadius": 5.0 + }, + { + "type": "text", + "x": 8.0, + "y": 8.0, + "width": 112.0, + "height": 112.0, + "text": "Graph", + "color": "#202020", + "fontSize": 12.0 + } + ] + }, + "properties": {}, + "library": { + "showSubtree": true + }, + "implementation": { + "kind": "graph", + "graph": { + "blocks": [ + { + "id": "89944479-e73e-446a-8d58-ebf35ac4144b", + "name": "New Graph Block 1", + "position": { + "x": 0.0, + "y": -96.0 + }, + "rotation": 0.0, + "interface": { + "inputs": [ + { + "id": "port-e5cb81a5", + "name": "Port 1", + "position": { + "x": 0.0, + "y": 0.0 + }, + "properties": { + "iconPosition": { + "x": 32.0, + "y": 64.0 + } + }, + "type": "signal" + } + ], + "outputs": [ + { + "id": "port-eb0034af", + "name": "Port 2", + "position": { + "x": 0.0, + "y": 0.0 + }, + "properties": { + "iconPosition": { + "x": 96.0, + "y": 64.0 + } + }, + "type": "signal" + } + ] + }, + "icon": { + "shape": "rectangle", + "fill": "#f4f4f4", + "border": "#303030", + "text": "Graph", + "size": { + "width": 128.0, + "height": 128.0 + }, + "elements": [ + { + "type": "rectangle", + "x": 32.0, + "y": 32.0, + "width": 64.0, + "height": 64.0, + "fill": "#f4f4f4", + "stroke": "#303030", + "lineWidth": 1.5, + "lineStyle": "solid", + "cornerRadius": 5.0 + }, + { + "type": "text", + "x": 40.0, + "y": 40.0, + "width": 48.0, + "height": 48.0, + "text": "Graph", + "color": "#303030", + "fontSize": 12.0, + "lineStyle": "solid", + "lineWidth": 1.5, + "stroke": "#303030", + "fill": "#ffffff" + } + ] + }, + "properties": {}, + "library": { + "showSubtree": true + }, + "implementation": { + "kind": "graph", + "graph": { + "blocks": [], + "connections": [] + } + } + } + ], + "connections": [] + } + } + } + ] +} diff --git a/BEdit/src/bedit/document/model.py b/BEdit/src/bedit/document/model.py index abba8eb..6a1d248 100644 --- a/BEdit/src/bedit/document/model.py +++ b/BEdit/src/bedit/document/model.py @@ -53,14 +53,14 @@ class Icon: if not self.elements: self.elements = [{ "type": "ellipse" if self.shape == "ellipse" else "rectangle", - "x": 1.0, "y": 1.0, "width": self.width - 2.0, "height": self.height - 2.0, + "x": 32.0, "y": 32.0, "width": 64.0, "height": 64.0, "fill": self.fill, "stroke": self.border, "lineWidth": 1.5, "lineStyle": "solid", "cornerRadius": 5.0, }] if self.text: self.elements.append({ - "type": "text", "x": 8.0, "y": 8.0, - "width": self.width - 16.0, "height": self.height - 16.0, + "type": "text", "x": 40.0, "y": 40.0, + "width": 48.0, "height": 48.0, "text": self.text, "color": "#202020", "fontSize": 12.0, }) diff --git a/BEdit/src/bedit/icon_renderer.py b/BEdit/src/bedit/icon_renderer.py index f6b070f..b260399 100644 --- a/BEdit/src/bedit/icon_renderer.py +++ b/BEdit/src/bedit/icon_renderer.py @@ -4,6 +4,22 @@ from PySide6.QtGui import QColor, QFont, QIcon, QPainter, QPen, QPixmap, QPolygo from bedit.document.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)), + max(0.0, float(element.get("width", 0))), + max(0.0, float(element.get("height", 0))), + ) + bounds = rect if bounds.isNull() else bounds.united(rect) + return bounds if not bounds.isNull() else QRectF(32, 32, 64, 64) + + def _pen(element: dict) -> QPen: styles = { "solid": Qt.PenStyle.SolidLine, @@ -19,11 +35,19 @@ def _pen(element: dict) -> QPen: ) -def paint_icon(painter: QPainter, icon: Icon, target: QRectF) -> None: +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() / icon.width, target.height() / icon.height) + 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( @@ -55,12 +79,20 @@ def icon_pixmap(icon: Icon, size: int = 16) -> QPixmap: pixmap = QPixmap(size, size) pixmap.fill(Qt.GlobalColor.transparent) painter = QPainter(pixmap) - ratio = min(size / icon.width, size / icon.height) - width, height = icon.width * ratio, icon.height * ratio - paint_icon(painter, icon, QRectF((size - width) / 2, (size - height) / 2, width, height)) + 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: - return QIcon(icon_pixmap(icon, 16)) + # 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)) diff --git a/BEdit/src/bedit/main_window.py b/BEdit/src/bedit/main_window.py index 7134c0c..4bb388a 100644 --- a/BEdit/src/bedit/main_window.py +++ b/BEdit/src/bedit/main_window.py @@ -31,6 +31,10 @@ class MainWindow(QMainWindow): super().__init__() self.ui = Ui_MainWindow() self.ui.setupUi(self) + self.ui.emptyPage.setStyleSheet("background-color: #9a9a9a;") + self.ui.emptyWorkspaceLabel.setStyleSheet( + "background: transparent; color: #202020;" + ) self._create_camera_toolbar() self.settings = QSettings() @@ -63,7 +67,8 @@ class MainWindow(QMainWindow): def _configure_models(self) -> None: self.ui.treeView.setModel(self.library_tree_model) - self.ui.treeView.setIconSize(QSize(16, 16)) + self.ui.treeView.setIconSize(QSize(28, 28)) + self.ui.treeView.setStyleSheet("QTreeView::item { height: 32px; }") self.ui.treeView.setHeaderHidden(True) self.ui.treeView.setDragEnabled(True) self.ui.treeView.setDragDropMode(self.ui.treeView.DragDropMode.DragOnly) @@ -379,6 +384,7 @@ class MainWindow(QMainWindow): scene = self.ui.graphView.scene() if scene is not None: scene.update() + self.ui.graphView.viewport().update() def _document_opened_changed(self, opened: bool) -> None: for action in (self.ui.actionClose, self.ui.actionSave, self.ui.actionSaveAs): diff --git a/BEdit/src/bedit/workspace/view.py b/BEdit/src/bedit/workspace/view.py index 175086b..cbde5ba 100644 --- a/BEdit/src/bedit/workspace/view.py +++ b/BEdit/src/bedit/workspace/view.py @@ -31,7 +31,7 @@ from PySide6.QtWidgets import ( from bedit.document.controller import DocumentController from bedit.document.model import Component, Connection, Endpoint, Port from bedit.library.tree_model import COMPONENT_MIME_TYPE -from bedit.icon_renderer import paint_icon +from bedit.icon_renderer import icon_bounds, paint_icon SELECTION_MIME_TYPE = "application/x-bedit-selection" @@ -67,6 +67,7 @@ class ComponentGraphicsItem(QGraphicsObject): self.component = component self.controller = controller self.drag_start = QPointF() + self.hitbox = icon_bounds(component.icon) self.setFlags( QGraphicsItem.GraphicsItemFlag.ItemIsMovable | QGraphicsItem.GraphicsItemFlag.ItemIsSelectable @@ -74,7 +75,7 @@ class ComponentGraphicsItem(QGraphicsObject): ) self.input_ports = self._create_ports(component.inputs, "target", 0.0) self.output_ports = self._create_ports(component.outputs, "source", self.WIDTH) - self.setTransformOriginPoint(self.WIDTH / 2, self.HEIGHT / 2) + self.setTransformOriginPoint(self.hitbox.center()) self.setRotation(component.rotation) def _create_ports(self, ports, role: str, x: float) -> dict[str, ConnectionPortItem]: @@ -92,7 +93,7 @@ class ComponentGraphicsItem(QGraphicsObject): return result def boundingRect(self) -> QRectF: # noqa: N802 - return QRectF(0, 0, self.WIDTH, self.HEIGHT) + return self.hitbox def paint( self, @@ -101,7 +102,11 @@ class ComponentGraphicsItem(QGraphicsObject): widget: QWidget | None = None, ) -> None: del option, widget - paint_icon(painter, self.component.icon, self.boundingRect()) + paint_icon( + painter, + self.component.icon, + QRectF(0, 0, self.WIDTH, self.HEIGHT), + ) if self.isSelected(): painter.setBrush(Qt.BrushStyle.NoBrush) painter.setPen(QPen(QColor("#2563eb"), 2, Qt.PenStyle.DashLine)) @@ -333,17 +338,8 @@ class GraphScene(QGraphicsScene): self.update_connection(connection.id) def drawBackground(self, painter: QPainter, rect: QRectF) -> None: # noqa: N802 - painter.fillRect(rect, QColor("#e4e4e4")) - if self.controller.document is None or self.controller.active_component is None: - return - spacing = _graph_grid_size() - left = int(rect.left()) - (int(rect.left()) % spacing) - top = int(rect.top()) - (int(rect.top()) % spacing) - painter.setPen(QPen(QColor("#b9c0c7"), 0)) - for x in range(left, int(rect.right()) + spacing, spacing): - painter.drawLine(x, rect.top(), x, rect.bottom()) - for y in range(top, int(rect.bottom()) + spacing, spacing): - painter.drawLine(rect.left(), y, rect.right(), y) + # The view paints the grid so it always covers the complete viewport. + del painter, rect def _endpoint_item(self, endpoint: Endpoint, role: str) -> ConnectionPortItem | None: if endpoint.interface is not None: @@ -412,10 +408,28 @@ class GraphWorkspaceView(QGraphicsView): self.setAcceptDrops(True) self.setRenderHint(QPainter.RenderHint.Antialiasing) self.setDragMode(QGraphicsView.DragMode.RubberBandDrag) - self.setBackgroundBrush(QColor("#9a9a9a")) + self.setBackgroundBrush(QColor("#f7f7f7")) self.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse) self.setResizeAnchor(QGraphicsView.ViewportAnchor.AnchorViewCenter) + def drawBackground(self, painter: QPainter, rect: QRectF) -> None: # noqa: N802 + """Paint the visible graph viewport in scene coordinates.""" + painter.fillRect(rect, QColor("#f7f7f7")) + if ( + self.controller is None + or self.controller.document is None + or self.controller.active_component is None + ): + return + spacing = _graph_grid_size() + left = int(rect.left()) - (int(rect.left()) % spacing) + top = int(rect.top()) - (int(rect.top()) % spacing) + painter.setPen(QPen(QColor("#c5cbd1"), 0)) + for x in range(left, int(rect.right()) + spacing, spacing): + painter.drawLine(QPointF(x, rect.top()), QPointF(x, rect.bottom())) + for y in range(top, int(rect.bottom()) + spacing, spacing): + painter.drawLine(QPointF(rect.left(), y), QPointF(rect.right(), y)) + def _zoom(self, factor: float) -> None: current = self.transform().m11() target = current * factor diff --git a/BEdit/test.bedit.json b/BEdit/test.bedit.json new file mode 100644 index 0000000..a79b1f5 --- /dev/null +++ b/BEdit/test.bedit.json @@ -0,0 +1,162 @@ +{ + "format": "bedit-document", + "version": 1, + "metadata": { + "name": "Untitled" + }, + "roots": [ + { + "id": "5ee742db-b25c-4d86-b4ca-cc0e61c4002a", + "name": "New Graph Block 1", + "position": { + "x": 0.0, + "y": 0.0 + }, + "rotation": 0.0, + "interface": { + "inputs": [], + "outputs": [] + }, + "icon": { + "shape": "rectangle", + "fill": "#f4f4f4", + "border": "#303030", + "text": "Graph", + "size": { + "width": 128.0, + "height": 128.0 + }, + "elements": [ + { + "type": "rectangle", + "x": 1.0, + "y": 1.0, + "width": 126.0, + "height": 126.0, + "fill": "#f4f4f4", + "stroke": "#303030", + "lineWidth": 1.5, + "lineStyle": "solid", + "cornerRadius": 5.0 + }, + { + "type": "text", + "x": 8.0, + "y": 8.0, + "width": 112.0, + "height": 112.0, + "text": "Graph", + "color": "#202020", + "fontSize": 12.0 + } + ] + }, + "properties": {}, + "library": { + "showSubtree": true + }, + "implementation": { + "kind": "graph", + "graph": { + "blocks": [ + { + "id": "89944479-e73e-446a-8d58-ebf35ac4144b", + "name": "New Graph Block 1", + "position": { + "x": 0.0, + "y": -96.0 + }, + "rotation": 0.0, + "interface": { + "inputs": [ + { + "id": "port-e5cb81a5", + "name": "Port 1", + "position": { + "x": 0.0, + "y": 0.0 + }, + "properties": { + "iconPosition": { + "x": 32.0, + "y": 64.0 + } + }, + "type": "signal" + } + ], + "outputs": [ + { + "id": "port-eb0034af", + "name": "Port 2", + "position": { + "x": 0.0, + "y": 0.0 + }, + "properties": { + "iconPosition": { + "x": 96.0, + "y": 64.0 + } + }, + "type": "signal" + } + ] + }, + "icon": { + "shape": "rectangle", + "fill": "#f4f4f4", + "border": "#303030", + "text": "Graph", + "size": { + "width": 128.0, + "height": 128.0 + }, + "elements": [ + { + "type": "rectangle", + "x": 32.0, + "y": 32.0, + "width": 64.0, + "height": 64.0, + "fill": "#f4f4f4", + "stroke": "#303030", + "lineWidth": 1.5, + "lineStyle": "solid", + "cornerRadius": 5.0 + }, + { + "type": "text", + "x": 40.0, + "y": 40.0, + "width": 48.0, + "height": 48.0, + "text": "Graph", + "color": "#303030", + "fontSize": 12.0, + "lineStyle": "solid", + "lineWidth": 1.5, + "stroke": "#303030", + "fill": "#ffffff" + } + ] + }, + "properties": {}, + "library": { + "showSubtree": true + }, + "implementation": { + "kind": "graph", + "graph": { + "blocks": [], + "connections": [] + } + } + } + ], + "connections": [] + } + } + } + ] +}