Start of graph view

This commit is contained in:
Joppe Blondel
2026-08-15 17:18:51 +02:00
parent 961a173a17
commit 0c93338e39
4 changed files with 194 additions and 3 deletions
@@ -0,0 +1,83 @@
from __future__ import annotations
from PySide6.QtCore import QEvent, QObject, QRectF, Qt
from PySide6.QtGui import QColor, QPainter, QPen, QWheelEvent
from PySide6.QtWidgets import QGraphicsScene, QGraphicsView, QWidget
from bedit_core.models import Component, GraphImplementation
from bedit_gui.ui.generated.ui_graph_editor_widget import Ui_graphEditorWidget
GRID_SPACING = 32
SCENE_SIZE = 10000
MIN_ZOOM = 0.2
MAX_ZOOM = 4.0
ZOOM_STEP = 1.15
class GraphGraphicsScene(QGraphicsScene):
"""Graph canvas with a lightweight dotted-line grid."""
def drawBackground(self, painter: QPainter, rect: QRectF) -> None:
painter.fillRect(rect, QColor("white"))
pen = QPen(QColor(205, 205, 205), 0, Qt.PenStyle.DotLine)
painter.setPen(pen)
left = int(rect.left()) - int(rect.left()) % GRID_SPACING
top = int(rect.top()) - int(rect.top()) % GRID_SPACING
x = left
while x <= rect.right():
painter.drawLine(x, rect.top(), x, rect.bottom())
x += GRID_SPACING
y = top
while y <= rect.bottom():
painter.drawLine(rect.left(), y, rect.right(), y)
y += GRID_SPACING
class GraphEditorWidget(QWidget):
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
self.ui = Ui_graphEditorWidget()
self.ui.setupUi(self)
self._component: Component | None = None
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.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse)
self.ui.graphicsView.viewport().installEventFilter(self)
self.ui.graphicsView.centerOn(0, 0)
def set_component(self, component: Component | None) -> None:
if component is not None and not isinstance(component.implementation, GraphImplementation):
raise TypeError("GraphEditorWidget only supports components with a graph implementation")
self._component = component
def component(self) -> Component | None:
return self._component
def eventFilter(self, watched: QObject, event: QEvent) -> bool:
if watched is self.ui.graphicsView.viewport() and event.type() == QEvent.Type.Wheel:
assert isinstance(event, QWheelEvent)
if event.modifiers() & Qt.KeyboardModifier.ControlModifier:
self._zoom(event)
return True
if event.modifiers() & Qt.KeyboardModifier.ShiftModifier:
self._scroll_horizontally(event)
return True
return super().eventFilter(watched, event)
def _zoom(self, event: QWheelEvent) -> None:
delta = event.angleDelta().y() or event.pixelDelta().y()
if not delta:
return
current_zoom = self.ui.graphicsView.transform().m11()
requested_zoom = current_zoom * ZOOM_STEP ** (delta / 120)
target_zoom = min(MAX_ZOOM, max(MIN_ZOOM, requested_zoom))
self.ui.graphicsView.scale(target_zoom / current_zoom, target_zoom / current_zoom)
def _scroll_horizontally(self, event: QWheelEvent) -> None:
scrollbar = self.ui.graphicsView.horizontalScrollBar()
pixel_delta = event.pixelDelta().y()
distance = pixel_delta if pixel_delta else event.angleDelta().y() / 120 * scrollbar.singleStep() * 3
scrollbar.setValue(scrollbar.value() - round(distance))