Start of graph view

This commit is contained in:
2026-08-15 17:18:51 +02:00
parent 961a173a17
commit 0c93338e39
4 changed files with 194 additions and 3 deletions

View File

@@ -96,7 +96,7 @@ class DocumentTreeController(QObject):
def _on_document_changed(self, model: CoreDocument) -> None:
"""Rebuild the tree whenever New/Open replaces the core document."""
self._show_equation_component(None)
self._show_component(None)
self.model.set_document(model)
self._components = {}
self._collect_components(model.root)
@@ -111,15 +111,21 @@ class DocumentTreeController(QObject):
def _selection_changed(self, *_args: object) -> None:
indexes = self.window.ui.documentTree.selectionModel().selectedRows(0)
component = self.model.value(indexes[0]) if len(indexes) == 1 else None
self._show_equation_component(component if isinstance(component, Component) else None)
self._show_component(component if isinstance(component, Component) else None)
def _show_equation_component(self, component: Component | None) -> None:
def _show_component(self, component: Component | None) -> None:
if component is not None and isinstance(component.implementation, EquationImplementation):
self.window.equation_editor.set_component(component)
self.window.equation_editor.show()
else:
self.window.equation_editor.set_component(None)
self.window.equation_editor.hide()
if component is not None and isinstance(component.implementation, GraphImplementation):
self.window.graph_editor.set_component(component)
self.window.graph_editor.show()
else:
self.window.graph_editor.set_component(None)
self.window.graph_editor.hide()
def _on_equation_text_changed(self, component: Component, section: str) -> None:
if self.window.equation_editor.component() is component:

View File

@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>graphEditorWidget</class>
<widget class="QWidget" name="graphEditorWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1079</width>
<height>730</height>
</rect>
</property>
<property name="windowTitle">
<string>Graph Editor</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QToolBar" name="graphToolBar">
<property name="windowTitle">
<string>Graph tools</string>
</property>
<property name="movable">
<bool>false</bool>
</property>
<property name="floatable">
<bool>false</bool>
</property>
<addaction name="actionSelect"/>
<addaction name="actionAddComponent"/>
<addaction name="actionAddConnection"/>
<addaction name="actionDelete"/>
</widget>
</item>
<item>
<widget class="QGraphicsView" name="graphicsView">
<property name="frameShape">
<enum>QFrame::Shape::NoFrame</enum>
</property>
<property name="dragMode">
<enum>QGraphicsView::DragMode::RubberBandDrag</enum>
</property>
<property name="renderHints">
<set>QPainter::RenderHint::Antialiasing</set>
</property>
</widget>
</item>
</layout>
<action name="actionSelect">
<property name="text">
<string>Select</string>
</property>
<property name="toolTip">
<string>Select graph items</string>
</property>
</action>
<action name="actionAddComponent">
<property name="text">
<string>Add Component</string>
</property>
<property name="toolTip">
<string>Add a component</string>
</property>
</action>
<action name="actionAddConnection">
<property name="text">
<string>Add Connection</string>
</property>
<property name="toolTip">
<string>Add a connection</string>
</property>
</action>
<action name="actionDelete">
<property name="text">
<string>Delete</string>
</property>
<property name="toolTip">
<string>Delete selected graph items</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -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))

View File

@@ -3,6 +3,7 @@ from PySide6.QtWidgets import QMainWindow, QTabWidget, QVBoxLayout
from bedit_gui.ui.generated.ui_main_window import Ui_MainWindow
from bedit_gui.views.equation_editor_widget import EquationEditorWidget
from bedit_gui.views.graph_editor_widget import GraphEditorWidget
class MainWindow(QMainWindow):
@@ -17,6 +18,9 @@ class MainWindow(QMainWindow):
self.equation_editor = EquationEditorWidget(self.ui.centralwidget)
self.equation_editor.hide()
central_layout.addWidget(self.equation_editor)
self.graph_editor = GraphEditorWidget(self.ui.centralwidget)
self.graph_editor.hide()
central_layout.addWidget(self.graph_editor)
self.setTabPosition(Qt.AllDockWidgetAreas, QTabWidget.North)
self.setCorner(Qt.Corner.BottomLeftCorner, Qt.DockWidgetArea.LeftDockWidgetArea)