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