Start of graph view
This commit is contained in:
@@ -96,7 +96,7 @@ class DocumentTreeController(QObject):
|
|||||||
|
|
||||||
def _on_document_changed(self, model: CoreDocument) -> None:
|
def _on_document_changed(self, model: CoreDocument) -> None:
|
||||||
"""Rebuild the tree whenever New/Open replaces the core document."""
|
"""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.model.set_document(model)
|
||||||
self._components = {}
|
self._components = {}
|
||||||
self._collect_components(model.root)
|
self._collect_components(model.root)
|
||||||
@@ -111,15 +111,21 @@ class DocumentTreeController(QObject):
|
|||||||
def _selection_changed(self, *_args: object) -> None:
|
def _selection_changed(self, *_args: object) -> None:
|
||||||
indexes = self.window.ui.documentTree.selectionModel().selectedRows(0)
|
indexes = self.window.ui.documentTree.selectionModel().selectedRows(0)
|
||||||
component = self.model.value(indexes[0]) if len(indexes) == 1 else None
|
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):
|
if component is not None and isinstance(component.implementation, EquationImplementation):
|
||||||
self.window.equation_editor.set_component(component)
|
self.window.equation_editor.set_component(component)
|
||||||
self.window.equation_editor.show()
|
self.window.equation_editor.show()
|
||||||
else:
|
else:
|
||||||
self.window.equation_editor.set_component(None)
|
self.window.equation_editor.set_component(None)
|
||||||
self.window.equation_editor.hide()
|
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:
|
def _on_equation_text_changed(self, component: Component, section: str) -> None:
|
||||||
if self.window.equation_editor.component() is component:
|
if self.window.equation_editor.component() is component:
|
||||||
|
|||||||
98
src/bedit_gui/ui/forms/graph_editor_widget.ui
Normal file
98
src/bedit_gui/ui/forms/graph_editor_widget.ui
Normal 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>
|
||||||
83
src/bedit_gui/views/graph_editor_widget.py
Normal file
83
src/bedit_gui/views/graph_editor_widget.py
Normal 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))
|
||||||
@@ -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.ui.generated.ui_main_window import Ui_MainWindow
|
||||||
from bedit_gui.views.equation_editor_widget import EquationEditorWidget
|
from bedit_gui.views.equation_editor_widget import EquationEditorWidget
|
||||||
|
from bedit_gui.views.graph_editor_widget import GraphEditorWidget
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(QMainWindow):
|
class MainWindow(QMainWindow):
|
||||||
@@ -17,6 +18,9 @@ class MainWindow(QMainWindow):
|
|||||||
self.equation_editor = EquationEditorWidget(self.ui.centralwidget)
|
self.equation_editor = EquationEditorWidget(self.ui.centralwidget)
|
||||||
self.equation_editor.hide()
|
self.equation_editor.hide()
|
||||||
central_layout.addWidget(self.equation_editor)
|
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.setTabPosition(Qt.AllDockWidgetAreas, QTabWidget.North)
|
||||||
self.setCorner(Qt.Corner.BottomLeftCorner, Qt.DockWidgetArea.LeftDockWidgetArea)
|
self.setCorner(Qt.Corner.BottomLeftCorner, Qt.DockWidgetArea.LeftDockWidgetArea)
|
||||||
|
|||||||
Reference in New Issue
Block a user