Start of graph view
This commit is contained in:
@@ -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))
|
||||
Reference in New Issue
Block a user