From 8892fd58dbb480e1e10e83d2072749a5d271731e Mon Sep 17 00:00:00 2001 From: John van de Wetering Date: Sun, 21 Jul 2024 17:44:14 -0300 Subject: [PATCH 01/11] Made previews only load once you first hover over the rewrite rule --- zxlive/rewrite_action.py | 111 ++++++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 47 deletions(-) diff --git a/zxlive/rewrite_action.py b/zxlive/rewrite_action.py index 5a245ee..f9795a5 100644 --- a/zxlive/rewrite_action.py +++ b/zxlive/rewrite_action.py @@ -34,7 +34,11 @@ class RewriteAction: matcher: Callable[[GraphT, Callable], list] rule: Callable[[GraphT, list], pyzx.rules.RewriteOutputType[VT, ET]] match_type: MatchType - tooltip: str + tooltip_str: str + picture_path: str = field(default=False) + #image: bytes = field(default=False) + lhs_graph: GraphT = field(default=False) + rhs_graph: GraphT = field(default=False) # Whether the graph should be copied before trying to test whether it matches. # Needed if the matcher changes the graph. copy_first: bool = field(default=False) @@ -44,57 +48,21 @@ class RewriteAction: @classmethod def from_rewrite_data(cls, d: RewriteData) -> RewriteAction: - if display_setting.previews_show and ('picture' in d or 'custom_rule' in d): - if 'custom_rule' in d: - # We will create a custom tooltip picture representing the custom rewrite - graph_scene_left = GraphScene() - graph_scene_right = GraphScene() - graph_view_left = GraphView(graph_scene_left) - graph_view_left.draw_background_lines = False - graph_view_left.set_graph(d['lhs']) - graph_view_right = GraphView(graph_scene_right) - graph_view_right.draw_background_lines = False - graph_view_right.set_graph(d['rhs']) - graph_view_left.fit_view() - graph_view_right.fit_view() - graph_view_left.setSceneRect(graph_scene_left.itemsBoundingRect()) - graph_view_right.setSceneRect(graph_scene_right.itemsBoundingRect()) - lhs_size = graph_view_left.viewport().size() - rhs_size = graph_view_right.viewport().size() - # The picture needs to be wide enough to fit both of them and have some space for the = sign - pixmap = QPixmap(lhs_size.width()+rhs_size.width()+160,max(lhs_size.height(),rhs_size.height())) - pixmap.fill(QColor("#ffffff")) - graph_view_left.viewport().render(pixmap) - graph_view_right.viewport().render(pixmap,QPoint(lhs_size.width()+160,0)) - # We create a new scene to render the = sign - new_scene = GraphScene() - new_view = GraphView(new_scene) - new_view.draw_background_lines = False - new_scene.addLine(QLineF(QPointF(10,40),QPointF(80,40)),QPen(QColor("#000000"),8)) - new_scene.addLine(QLineF(QPointF(10,10),QPointF(80,10)),QPen(QColor("#000000"),8)) - new_view.setSceneRect(new_scene.itemsBoundingRect()) - new_view.viewport().render(pixmap,QPoint(lhs_size.width(),max(lhs_size.height(),rhs_size.height())/2-20)) - - buffer = QBuffer() - buffer.open(QIODevice.WriteOnly) - pixmap.save(buffer, "PNG", quality=100) - image = bytes(buffer.data().toBase64()).decode() - else: - pixmap = QPixmap() - pixmap.load(get_data("tooltips/"+d['picture'])) - buffer = QBuffer() - buffer.open(QIODevice.WriteOnly) - pixmap.save(buffer, "PNG", quality=100) - image = bytes(buffer.data().toBase64()).decode() - tooltip = ''.format(image) + d['tooltip'] - else: - tooltip = d['tooltip'] + if 'custom_rule' in d: + picture_path = 'custom' + elif 'picture' in d: + picture_path = d['picture'] + else: + picture_path = None return cls( name=d['text'], matcher=d['matcher'], rule=d['rule'], match_type=d['type'], - tooltip=tooltip, + tooltip_str=d['tooltip'], + picture_path = picture_path, + lhs_graph = d.get('lhs', None), + rhs_graph = d.get('rhs', None), copy_first=d.get('copy_first', False), returns_new_graph=d.get('returns_new_graph', False), ) @@ -140,6 +108,55 @@ def update_active(self, g: GraphT, verts: list[VT], edges: list[ET]) -> None: else self.matcher(g, lambda e: e in edges) ) + @property + def tooltip(self): + if self.picture_path is None: + return self.tooltip_str + if self.picture_path == 'custom': + # We will create a custom tooltip picture representing the custom rewrite + graph_scene_left = GraphScene() + graph_scene_right = GraphScene() + graph_view_left = GraphView(graph_scene_left) + graph_view_left.draw_background_lines = False + graph_view_left.set_graph(self.lhs_graph) + graph_view_right = GraphView(graph_scene_right) + graph_view_right.draw_background_lines = False + graph_view_right.set_graph(self.rhs_graph) + graph_view_left.fit_view() + graph_view_right.fit_view() + graph_view_left.setSceneRect(graph_scene_left.itemsBoundingRect()) + graph_view_right.setSceneRect(graph_scene_right.itemsBoundingRect()) + lhs_size = graph_view_left.viewport().size() + rhs_size = graph_view_right.viewport().size() + # The picture needs to be wide enough to fit both of them and have some space for the = sign + pixmap = QPixmap(lhs_size.width()+rhs_size.width()+160,max(lhs_size.height(),rhs_size.height())) + pixmap.fill(QColor("#ffffff")) + graph_view_left.viewport().render(pixmap) + graph_view_right.viewport().render(pixmap,QPoint(lhs_size.width()+160,0)) + # We create a new scene to render the = sign + new_scene = GraphScene() + new_view = GraphView(new_scene) + new_view.draw_background_lines = False + new_scene.addLine(QLineF(QPointF(10,40),QPointF(80,40)),QPen(QColor("#000000"),8)) + new_scene.addLine(QLineF(QPointF(10,10),QPointF(80,10)),QPen(QColor("#000000"),8)) + new_view.setSceneRect(new_scene.itemsBoundingRect()) + new_view.viewport().render(pixmap,QPoint(lhs_size.width(),max(lhs_size.height(),rhs_size.height())/2-20)) + + buffer = QBuffer() + buffer.open(QIODevice.WriteOnly) + pixmap.save(buffer, "PNG", quality=100) + image = bytes(buffer.data().toBase64()).decode() + else: + pixmap = QPixmap() + pixmap.load(get_data("tooltips/"+self.picture_path)) + buffer = QBuffer() + buffer.open(QIODevice.WriteOnly) + pixmap.save(buffer, "PNG", quality=100) + image = bytes(buffer.data().toBase64()).decode() + self.tooltip_str = ''.format(image) + self.tooltip_str + self.picture_path = None + return self.tooltip_str + @dataclass class RewriteActionTree: From 31277c605c9b1e228da011998b7bc59bec26d4dd Mon Sep 17 00:00:00 2001 From: John van de Wetering Date: Sun, 21 Jul 2024 20:12:54 -0300 Subject: [PATCH 02/11] Add button to automatically snap vertices to edges underneath them --- zxlive/commands.py | 43 ++++++++++++++++++++++++++++++++ zxlive/editor_base_panel.py | 49 +++++++++++++++++++++++++++++-------- zxlive/graphscene.py | 6 +++-- 3 files changed, 86 insertions(+), 12 deletions(-) diff --git a/zxlive/commands.py b/zxlive/commands.py index 6865971..b30d184 100644 --- a/zxlive/commands.py +++ b/zxlive/commands.py @@ -208,6 +208,49 @@ def redo(self) -> None: self._added_vert = self.g.add_vertex(self.vty, y,x) self.update_graph_view() +@dataclass +class AddNodeSnapped(BaseCommand): + """Adds a new spider positioned on an edge, replacing the original edge""" + x: float + y: float + vty: VertexType + e: ET + + _added_vert: Optional[VT] = field(default=None, init=False) + _removed_edge: Optional[ET] = field(default=None, init=False) + _s: Optional[VT] = field(default=None, init=False) + _t: Optional[VT] = field(default=None, init=False) + _et: Optional[EdgeType] = field(default=None, init=False) + + def undo(self) -> None: + assert self._added_vert is not None + assert self._s is not None + assert self._t is not None + assert self._et is not None + self.g.remove_vertex(self._added_vert) + self.g.add_edge(self.g.edge(self._s,self._t), self._et) + self.update_graph_view() + + def redo(self) -> None: + y = round(self.y * display_setting.SNAP_DIVISION) / display_setting.SNAP_DIVISION + x = round(self.x * display_setting.SNAP_DIVISION) / display_setting.SNAP_DIVISION + self._added_vert = self.g.add_vertex(self.vty, y,x) + s,t = self.g.edge_st(self.e) + self._et = self.g.edge_type(self.e) + if self._et == EdgeType.SIMPLE: + self.g.add_edge(self.g.edge(s, self._added_vert), EdgeType.SIMPLE) + self.g.add_edge(self.g.edge(t, self._added_vert), EdgeType.SIMPLE) + elif self._et == EdgeType.HADAMARD: + self.g.add_edge(self.g.edge(s, self._added_vert), EdgeType.HADAMARD) + self.g.add_edge(self.g.edge(t, self._added_vert), EdgeType.SIMPLE) + else: + raise ValueError("Can't add spider between vertices connected by edge of type", str(self._et)) + self._s = s + self._t = t + + self.g.remove_edge(self.e) + self.update_graph_view() + @dataclass class AddWNode(BaseCommand): """Adds a new W node at a given position.""" diff --git a/zxlive/editor_base_panel.py b/zxlive/editor_base_panel.py index 6d1c218..1822d4e 100644 --- a/zxlive/editor_base_panel.py +++ b/zxlive/editor_base_panel.py @@ -4,9 +4,9 @@ from enum import Enum from typing import Callable, Iterator, TypedDict -from PySide6.QtCore import QPoint, QSize, Qt, Signal +from PySide6.QtCore import QPoint, QPointF, QSize, Qt, Signal from PySide6.QtGui import (QAction, QColor, QIcon, QPainter, QPalette, QPen, - QPixmap) + QPixmap, QTransform) from PySide6.QtWidgets import (QApplication, QComboBox, QFrame, QGridLayout, QInputDialog, QLabel, QListView, QListWidget, QListWidgetItem, QScrollArea, QSizePolicy, @@ -17,12 +17,12 @@ from zxlive.sfx import SFXEnum from .base_panel import BasePanel, ToolbarSection -from .commands import (AddEdge, AddNode, AddWNode, ChangeEdgeColor, +from .commands import (AddEdge, AddNode, AddNodeSnapped, AddWNode, ChangeEdgeColor, ChangeNodeType, ChangePhase, MoveNode, SetGraph, UpdateGraph) from .common import VT, GraphT, ToolType, get_data from .dialogs import show_error_msg -from .eitem import HAD_EDGE_BLUE +from .eitem import EItem, HAD_EDGE_BLUE from .graphscene import EditGraphScene from .settings import display_setting from .vitem import BLACK @@ -67,6 +67,7 @@ class EditorBasePanel(BasePanel): _curr_ety: EdgeType _curr_vty: VertexType + snap_vertex_edge = True def __init__(self, *actions: QAction) -> None: super().__init__(*actions) @@ -74,7 +75,7 @@ def __init__(self, *actions: QAction) -> None: self._curr_ety = EdgeType.SIMPLE def _toolbar_sections(self) -> Iterator[ToolbarSection]: - yield toolbar_select_node_edge(self) + yield from toolbar_select_node_edge(self) yield ToolbarSection(*self.actions()) def create_side_bar(self) -> None: @@ -98,6 +99,9 @@ def update_colors(self) -> None: def _tool_clicked(self, tool: ToolType) -> None: self.graph_scene.curr_tool = tool + def _snap_vertex_edge_clicked(self) -> None: + self.snap_vertex_edge = not self.snap_vertex_edge + def _vty_clicked(self, vty: VertexType) -> None: self._curr_vty = vty @@ -144,9 +148,25 @@ def delete_selection(self) -> None: else UpdateGraph(self.graph_view,new_g) self.undo_stack.push(cmd) - def add_vert(self, x: float, y: float) -> None: - cmd = AddWNode(self.graph_view, x, y) if self._curr_vty == VertexType.W_OUTPUT \ - else AddNode(self.graph_view, x, y, self._curr_vty) + def add_vert(self, x: float, y: float, edges: list[EItem]) -> None: + """Add a vertex at point (x,y). `edges` is a list of EItems that are underneath the current position. + We will try to connect the vertex to an edge. + """ + if self.snap_vertex_edge and edges and self._curr_vty != VertexType.W_OUTPUT: + # Trying to snap vertex to an edge + for it in edges: + e = it.e + if self.graph_scene.g.edge_type(e) not in (EdgeType.SIMPLE, EdgeType.HADAMARD): + continue + cmd = AddNodeSnapped(self.graph_view, x, y, self._curr_vty, e) + break + else: + cmd = AddWNode(self.graph_view, x, y) if self._curr_vty == VertexType.W_OUTPUT \ + else AddNode(self.graph_view, x, y, self._curr_vty) + else: + cmd = AddWNode(self.graph_view, x, y) if self._curr_vty == VertexType.W_OUTPUT \ + else AddNode(self.graph_view, x, y, self._curr_vty) + self.play_sound_signal.emit(SFXEnum.THATS_A_SPIDER) self.undo_stack.push(cmd) @@ -289,7 +309,7 @@ def _text_changed(self, name: str, text: str) -> None: self.parent_panel.graph.variable_types[name] = True -def toolbar_select_node_edge(parent: EditorBasePanel) -> ToolbarSection: +def toolbar_select_node_edge(parent: EditorBasePanel) -> Iterator[ToolbarSection]: icon_size = QSize(32, 32) select = QToolButton(parent) # Selected by default vertex = QToolButton(parent) @@ -313,7 +333,16 @@ def toolbar_select_node_edge(parent: EditorBasePanel) -> ToolbarSection: select.clicked.connect(lambda: parent._tool_clicked(ToolType.SELECT)) vertex.clicked.connect(lambda: parent._tool_clicked(ToolType.VERTEX)) edge.clicked.connect(lambda: parent._tool_clicked(ToolType.EDGE)) - return ToolbarSection(select, vertex, edge, exclusive=True) + yield ToolbarSection(select, vertex, edge, exclusive=True) + + snap = QToolButton(parent) + snap.setCheckable(True) + snap.setChecked(True) + snap.setText("Vertex-edge snap") + snap.setToolTip("Snap newly added vertex to the edge beneath it (f)") + snap.setShortcut("f") + snap.clicked.connect(lambda: parent._snap_vertex_edge_clicked()) + yield ToolbarSection(snap) def create_list_widget(parent: EditorBasePanel, diff --git a/zxlive/graphscene.py b/zxlive/graphscene.py index 118b74c..2b2a10b 100644 --- a/zxlive/graphscene.py +++ b/zxlive/graphscene.py @@ -231,7 +231,7 @@ class EditGraphScene(GraphScene): # Signals to handle addition of vertices and edges. # Note that we have to set the argument types to `object`, # otherwise it doesn't work for some reason... - vertex_added = Signal(object, object) # Actual types: float, float + vertex_added = Signal(object, object, object) # Actual types: float, float, list[EItem] edge_added = Signal(object, object) # Actual types: VT, VT # Currently selected edge type for preview when dragging @@ -291,7 +291,9 @@ def mouseReleaseEvent(self, e: QGraphicsSceneMouseEvent) -> None: def add_vertex(self, e: QGraphicsSceneMouseEvent) -> None: p = e.scenePos() - self.vertex_added.emit(*pos_from_view(p.x(), p.y())) + # edges under current mouse position + edges: list[EItem] = [e for e in self.items(p, deviceTransform=QTransform()) if isinstance(e,EItem)] + self.vertex_added.emit(*pos_from_view(p.x(), p.y()), edges) def add_edge(self, e: QGraphicsSceneMouseEvent) -> None: assert self._drag is not None From 054b5cab001915bdb0f229b122a7479e9257ef49 Mon Sep 17 00:00:00 2001 From: John van de Wetering Date: Sun, 21 Jul 2024 21:41:43 -0300 Subject: [PATCH 03/11] Add animation to edge when adding a snapped vertex --- zxlive/animations.py | 15 +++++++ zxlive/commands.py | 31 +++++++------ zxlive/editor_base_panel.py | 25 +++++++---- zxlive/eitem.py | 87 +++++++++++++++++++++++++++++++++++-- zxlive/graphscene.py | 2 + 5 files changed, 133 insertions(+), 27 deletions(-) diff --git a/zxlive/animations.py b/zxlive/animations.py index bd6aa53..d384f57 100644 --- a/zxlive/animations.py +++ b/zxlive/animations.py @@ -14,6 +14,7 @@ from .common import VT, GraphT, pos_to_view, ANIMATION_DURATION from .graphscene import GraphScene from .vitem import VItem, VItemAnimation, VITEM_UNSELECTED_Z, VITEM_SELECTED_Z, get_w_partner_vitem +from .eitem import EItem, EItemAnimation if TYPE_CHECKING: from .proof_panel import ProofPanel @@ -69,6 +70,13 @@ def _push_now(self, cmd: QUndoCommand, anim_after: Optional[QAbstractAnimation] anim_after.start() self.running_anim = anim_after + def set_anim(self, anim: QAbstractAnimation) -> None: + if self.running_anim: + self.running_anim.stop() + self.running_anim = anim + self.running_anim.start() + + def scale(it: VItem, target: float, duration: int, ease: QEasingCurve, start: Optional[float] = None) -> VItemAnimation: anim = VItemAnimation(it, VItem.Properties.Scale) @@ -89,6 +97,13 @@ def move(it: VItem, target: QPointF, duration: int, ease: QEasingCurve, start: O anim.setEasingCurve(ease) return anim +def edge_thickness(it: EItem, target: float, duration: int, ease: QEasingCurve, start: Optional[float] = None) -> EItemAnimation: + anim = EItemAnimation(it, EItem.Properties.Thickness, refresh=True) + anim.setDuration(duration) + anim.setStartValue(start or it.thickness) + anim.setEndValue(target) + anim.setEasingCurve(ease) + return anim def morph_graph(start: GraphT, end: GraphT, scene: GraphScene, to_start: Callable[[VT], Optional[VT]], to_end: Callable[[VT], Optional[VT]], duration: int, ease: QEasingCurve) -> QAbstractAnimation: diff --git a/zxlive/commands.py b/zxlive/commands.py index b30d184..961b05a 100644 --- a/zxlive/commands.py +++ b/zxlive/commands.py @@ -216,37 +216,36 @@ class AddNodeSnapped(BaseCommand): vty: VertexType e: ET - _added_vert: Optional[VT] = field(default=None, init=False) - _removed_edge: Optional[ET] = field(default=None, init=False) - _s: Optional[VT] = field(default=None, init=False) - _t: Optional[VT] = field(default=None, init=False) + added_vert: Optional[VT] = field(default=None, init=False) + s: Optional[VT] = field(default=None, init=False) + t: Optional[VT] = field(default=None, init=False) _et: Optional[EdgeType] = field(default=None, init=False) def undo(self) -> None: - assert self._added_vert is not None - assert self._s is not None - assert self._t is not None + assert self.added_vert is not None + assert self.s is not None + assert self.t is not None assert self._et is not None - self.g.remove_vertex(self._added_vert) - self.g.add_edge(self.g.edge(self._s,self._t), self._et) + self.g.remove_vertex(self.added_vert) + self.g.add_edge(self.g.edge(self.s,self.t), self._et) self.update_graph_view() def redo(self) -> None: y = round(self.y * display_setting.SNAP_DIVISION) / display_setting.SNAP_DIVISION x = round(self.x * display_setting.SNAP_DIVISION) / display_setting.SNAP_DIVISION - self._added_vert = self.g.add_vertex(self.vty, y,x) + self.added_vert = self.g.add_vertex(self.vty, y,x) s,t = self.g.edge_st(self.e) self._et = self.g.edge_type(self.e) if self._et == EdgeType.SIMPLE: - self.g.add_edge(self.g.edge(s, self._added_vert), EdgeType.SIMPLE) - self.g.add_edge(self.g.edge(t, self._added_vert), EdgeType.SIMPLE) + self.g.add_edge(self.g.edge(s, self.added_vert), EdgeType.SIMPLE) + self.g.add_edge(self.g.edge(t, self.added_vert), EdgeType.SIMPLE) elif self._et == EdgeType.HADAMARD: - self.g.add_edge(self.g.edge(s, self._added_vert), EdgeType.HADAMARD) - self.g.add_edge(self.g.edge(t, self._added_vert), EdgeType.SIMPLE) + self.g.add_edge(self.g.edge(s, self.added_vert), EdgeType.HADAMARD) + self.g.add_edge(self.g.edge(t, self.added_vert), EdgeType.SIMPLE) else: raise ValueError("Can't add spider between vertices connected by edge of type", str(self._et)) - self._s = s - self._t = t + self.s = s + self.t = t self.g.remove_edge(self.e) self.update_graph_view() diff --git a/zxlive/editor_base_panel.py b/zxlive/editor_base_panel.py index 1822d4e..0336d09 100644 --- a/zxlive/editor_base_panel.py +++ b/zxlive/editor_base_panel.py @@ -4,7 +4,7 @@ from enum import Enum from typing import Callable, Iterator, TypedDict -from PySide6.QtCore import QPoint, QPointF, QSize, Qt, Signal +from PySide6.QtCore import QPoint, QPointF, QSize, Qt, Signal, QEasingCurve, QParallelAnimationGroup from PySide6.QtGui import (QAction, QColor, QIcon, QPainter, QPalette, QPen, QPixmap, QTransform) from PySide6.QtWidgets import (QApplication, QComboBox, QFrame, QGridLayout, @@ -22,10 +22,11 @@ UpdateGraph) from .common import VT, GraphT, ToolType, get_data from .dialogs import show_error_msg -from .eitem import EItem, HAD_EDGE_BLUE +from .eitem import EItem, HAD_EDGE_BLUE, EItemAnimation from .graphscene import EditGraphScene from .settings import display_setting from .vitem import BLACK +from . import animations class ShapeType(Enum): @@ -156,15 +157,23 @@ def add_vert(self, x: float, y: float, edges: list[EItem]) -> None: # Trying to snap vertex to an edge for it in edges: e = it.e + g = self.graph_scene.g if self.graph_scene.g.edge_type(e) not in (EdgeType.SIMPLE, EdgeType.HADAMARD): continue cmd = AddNodeSnapped(self.graph_view, x, y, self._curr_vty, e) - break - else: - cmd = AddWNode(self.graph_view, x, y) if self._curr_vty == VertexType.W_OUTPUT \ - else AddNode(self.graph_view, x, y, self._curr_vty) - else: - cmd = AddWNode(self.graph_view, x, y) if self._curr_vty == VertexType.W_OUTPUT \ + self.play_sound_signal.emit(SFXEnum.THATS_A_SPIDER) + self.undo_stack.push(cmd) + g = cmd.g + group = QParallelAnimationGroup() + for e in [next(g.edges(cmd.s, cmd.added_vert)), next(g.edges(cmd.t, cmd.added_vert))]: + eitem = self.graph_scene.edge_map[e][0] + anim = animations.edge_thickness(eitem,3,400, + QEasingCurve(QEasingCurve.Type.InCubic),start=7) + group.addAnimation(anim) + self.undo_stack.set_anim(group) + return + + cmd = AddWNode(self.graph_view, x, y) if self._curr_vty == VertexType.W_OUTPUT \ else AddNode(self.graph_view, x, y, self._curr_vty) self.play_sound_signal.emit(SFXEnum.THATS_A_SPIDER) diff --git a/zxlive/eitem.py b/zxlive/eitem.py index b2a72e1..a117ed8 100644 --- a/zxlive/eitem.py +++ b/zxlive/eitem.py @@ -15,9 +15,10 @@ from __future__ import annotations from math import sqrt -from typing import Optional, Any, TYPE_CHECKING +from typing import Optional, Any, TYPE_CHECKING, Union +from enum import Enum -from PySide6.QtCore import QPointF +from PySide6.QtCore import QPointF, QVariantAnimation, QAbstractAnimation from PySide6.QtWidgets import QGraphicsEllipseItem, QGraphicsPathItem, QGraphicsItem, \ QGraphicsSceneMouseEvent, QStyleOptionGraphicsItem, QWidget, QStyle from PySide6.QtGui import QPen, QPainter, QColor, QPainterPath @@ -35,6 +36,13 @@ class EItem(QGraphicsPathItem): """A QGraphicsItem representing an edge""" + # Set of animations that are currently running on this vertex + active_animations: set[EItemAnimation] + + class Properties(Enum): + """Properties of an EItem that can be animated.""" + Thickness = 1 + def __init__(self, graph_scene: GraphScene, e: ET, s_item: VItem, t_item: VItem, curve_distance: float = 0) -> None: super().__init__() self.setZValue(EITEM_Z) @@ -46,6 +54,7 @@ def __init__(self, graph_scene: GraphScene, e: ET, s_item: VItem, t_item: VItem, self.s_item = s_item self.t_item = t_item self.curve_distance = curve_distance + self.active_animations = set() s_item.adj_items.add(self) t_item.adj_items.add(self) self.selection_node = QGraphicsEllipseItem(-0.1 * SCALE, -0.1 * SCALE, 0.2 * SCALE, 0.2 * SCALE) @@ -58,6 +67,7 @@ def __init__(self, graph_scene: GraphScene, e: ET, s_item: VItem, t_item: VItem, self.is_mouse_pressed = False self.is_dragging = False self._old_pos: Optional[QPointF] = None + self.thickness: float = 3 self.refresh() @@ -65,6 +75,10 @@ def __init__(self, graph_scene: GraphScene, e: ET, s_item: VItem, t_item: VItem, def g(self) -> GraphT: return self.graph_scene.g + @property + def is_animated(self) -> bool: + return len(self.active_animations) > 0 + def refresh(self) -> None: """Call whenever source or target moves or edge data changes""" @@ -72,7 +86,7 @@ def refresh(self) -> None: self.g.edge_type(self.e) != EdgeType.W_IO) # set color/style according to edge type pen = QPen() - pen.setWidthF(3) + pen.setWidthF(self.thickness) if self.g.edge_type(self.e) == EdgeType.HADAMARD: pen.setColor(QColor(HAD_EDGE_BLUE)) pen.setDashPattern([4.0, 2.0]) @@ -197,3 +211,70 @@ def compute_perpendicular_direction(source_pos: QPointF, target_pos: QPointF) -> direction = direction / norm perpendicular = QPointF(-direction.y(), direction.x()) return perpendicular + + +class EItemAnimation(QVariantAnimation): + """Animator for edge graphics items. + + This animator lets the edge know that its being animated which stops any + interaction with the user. Furthermore, this animator + ensures that it's not garbage collected until the animation is finished, so there is + no need to hold onto a reference of this class.""" + + _it: Optional[EItem] + prop: EItem.Properties + refresh: bool # Whether the item is refreshed at each frame + + e: Optional[ET] + + def __init__(self, item: Union[EItem, ET], property: EItem.Properties, + scene: Optional[GraphScene] = None, refresh: bool = False) -> None: + super().__init__() + self.e = None + self._it = None + self.scene: Optional[GraphScene] = None + #if refresh and property != VItem.Properties.Position: + # raise ValueError("Only position animations require refresh") + if isinstance(item, EItem): + self._it = item + elif scene is None: + raise ValueError("Scene is required to obtain EItem from edge ET") + else: + self.e = item + self.scene = scene + self.prop = property + self.refresh = refresh + self.stateChanged.connect(self._on_state_changed) + + @property + def it(self) -> EItem: + if self._it is None and self.scene is not None and self.e is not None: + self._it = self.scene.edge_map[self.e] + assert self._it is not None + return self._it + + def _on_state_changed(self, state: QAbstractAnimation.State) -> None: + if state == QAbstractAnimation.State.Running and self not in self.it.active_animations: + # Stop all animations that target the same property + for anim in self.it.active_animations.copy(): + if anim.prop == self.prop: + anim.stop() + self.it.active_animations.add(self) + elif state == QAbstractAnimation.State.Stopped: + self.it.active_animations.remove(self) + elif state == QAbstractAnimation.State.Paused: + # TODO: Once we use pausing, we should decide what to do here. + # Note that we cannot just remove ourselves from the set since the garbage + # collector will eat us in that case. We'll probably need something like + # `it.paused_animations` + pass + + def updateCurrentValue(self, value: Any) -> None: + if self.state() != QAbstractAnimation.State.Running: + return + + if self.prop == EItem.Properties.Thickness: + self.it.thickness = value + + if self.refresh: + self.it.refresh() diff --git a/zxlive/graphscene.py b/zxlive/graphscene.py index 2b2a10b..f639a11 100644 --- a/zxlive/graphscene.py +++ b/zxlive/graphscene.py @@ -120,6 +120,8 @@ def update_graph(self, new: GraphT, select_new: bool = False) -> None: e_item = self.edge_map[e][edge_idx] if e_item.selection_node: self.removeItem(e_item.selection_node) + for anim in e_item.active_animations.copy(): + anim.stop() self.removeItem(e_item) self.edge_map[e].pop(edge_idx) s, t = self.g.edge_st(e) From ac2d41ae0e983a6631763aac64face88786132fd Mon Sep 17 00:00:00 2001 From: Razin Shaikh Date: Wed, 31 Jul 2024 20:09:06 +0100 Subject: [PATCH 04/11] create a rectangle around mouse position to check for interesection with edges to snap vertex to edge --- zxlive/graphscene.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/zxlive/graphscene.py b/zxlive/graphscene.py index f639a11..41ccf15 100644 --- a/zxlive/graphscene.py +++ b/zxlive/graphscene.py @@ -17,16 +17,17 @@ from typing import Optional, Iterator, Iterable -from PySide6.QtCore import Qt, Signal +from PySide6.QtCore import Qt, Signal, QRectF from PySide6.QtGui import QBrush, QColor, QTransform from PySide6.QtWidgets import QGraphicsScene, QGraphicsSceneMouseEvent, QGraphicsItem from pyzx.graph.base import EdgeType from pyzx.graph import GraphDiff -from .common import VT, ET, GraphT, ToolType, pos_from_view, OFFSET_X, OFFSET_Y +from .common import SCALE, VT, ET, GraphT, ToolType, pos_from_view, OFFSET_X, OFFSET_Y from .vitem import VItem from .eitem import EItem, EDragItem +from .settings import display_setting class GraphScene(QGraphicsScene): @@ -293,8 +294,11 @@ def mouseReleaseEvent(self, e: QGraphicsSceneMouseEvent) -> None: def add_vertex(self, e: QGraphicsSceneMouseEvent) -> None: p = e.scenePos() + # create a rectangle around the mouse position which will be used to check of edge intersections + snap = display_setting.SNAP_DIVISION + rect = QRectF(p.x() - SCALE/(2*snap), p.y() - SCALE/(2*snap), SCALE/snap, SCALE/snap) # edges under current mouse position - edges: list[EItem] = [e for e in self.items(p, deviceTransform=QTransform()) if isinstance(e,EItem)] + edges: list[EItem] = [e for e in self.items(rect, deviceTransform=QTransform()) if isinstance(e,EItem)] self.vertex_added.emit(*pos_from_view(p.x(), p.y()), edges) def add_edge(self, e: QGraphicsSceneMouseEvent) -> None: From 67201569a50bb9cf06b7f08fdb60c8f063affd04 Mon Sep 17 00:00:00 2001 From: John van de Wetering Date: Thu, 1 Aug 2024 21:26:31 +0200 Subject: [PATCH 05/11] Added icon to snap-to-edge tool --- zxlive/editor_base_panel.py | 5 +++-- zxlive/eitem.py | 2 -- zxlive/icons/vertex-snap-to-edge.svg | 11 +++++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 zxlive/icons/vertex-snap-to-edge.svg diff --git a/zxlive/editor_base_panel.py b/zxlive/editor_base_panel.py index 0336d09..1a2df29 100644 --- a/zxlive/editor_base_panel.py +++ b/zxlive/editor_base_panel.py @@ -347,8 +347,9 @@ def toolbar_select_node_edge(parent: EditorBasePanel) -> Iterator[ToolbarSection snap = QToolButton(parent) snap.setCheckable(True) snap.setChecked(True) - snap.setText("Vertex-edge snap") - snap.setToolTip("Snap newly added vertex to the edge beneath it (f)") + #snap.setText("Vertex-edge snap") + snap.setIcon(QIcon(get_data("icons/vertex-snap-to-edge.svg"))) + snap.setToolTip("Snap newly added vertices to the edge beneath them (f)") snap.setShortcut("f") snap.clicked.connect(lambda: parent._snap_vertex_edge_clicked()) yield ToolbarSection(snap) diff --git a/zxlive/eitem.py b/zxlive/eitem.py index a117ed8..ac86747 100644 --- a/zxlive/eitem.py +++ b/zxlive/eitem.py @@ -233,8 +233,6 @@ def __init__(self, item: Union[EItem, ET], property: EItem.Properties, self.e = None self._it = None self.scene: Optional[GraphScene] = None - #if refresh and property != VItem.Properties.Position: - # raise ValueError("Only position animations require refresh") if isinstance(item, EItem): self._it = item elif scene is None: diff --git a/zxlive/icons/vertex-snap-to-edge.svg b/zxlive/icons/vertex-snap-to-edge.svg new file mode 100644 index 0000000..2f0a30d --- /dev/null +++ b/zxlive/icons/vertex-snap-to-edge.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file From e11734dae357b9fd903680070613bab51a2fc6fa Mon Sep 17 00:00:00 2001 From: John van de Wetering Date: Thu, 1 Aug 2024 22:13:57 +0200 Subject: [PATCH 06/11] Now newly added edges also snap to all underlying vertices! --- zxlive/commands.py | 18 +++++++++++++++++- zxlive/editor_base_panel.py | 27 +++++++++++++++++++++++---- zxlive/graphscene.py | 19 +++++++++++++++---- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/zxlive/commands.py b/zxlive/commands.py index 961b05a..9ac9ebf 100644 --- a/zxlive/commands.py +++ b/zxlive/commands.py @@ -286,7 +286,23 @@ def undo(self) -> None: self.update_graph_view() def redo(self) -> None: - self.g.add_edge(((self.u, self.v)), self.ety) + self.g.add_edge((self.u, self.v), self.ety) + self.update_graph_view() + +@dataclass +class AddEdges(BaseCommand): + """Adds multiple edges of the same type to a graph.""" + pairs: list[tuple[VT,VT]] + ety: EdgeType + + def undo(self) -> None: + for u, v in self.pairs: + self.g.remove_edge((u, v, self.ety)) + self.update_graph_view() + + def redo(self) -> None: + for u, v in self.pairs: + self.g.add_edge((u, v), self.ety) self.update_graph_view() diff --git a/zxlive/editor_base_panel.py b/zxlive/editor_base_panel.py index 1a2df29..f6d5a6d 100644 --- a/zxlive/editor_base_panel.py +++ b/zxlive/editor_base_panel.py @@ -17,15 +17,16 @@ from zxlive.sfx import SFXEnum from .base_panel import BasePanel, ToolbarSection -from .commands import (AddEdge, AddNode, AddNodeSnapped, AddWNode, ChangeEdgeColor, +from .commands import (AddEdge, AddEdges, AddNode, AddNodeSnapped, AddWNode, ChangeEdgeColor, ChangeNodeType, ChangePhase, MoveNode, SetGraph, UpdateGraph) from .common import VT, GraphT, ToolType, get_data from .dialogs import show_error_msg from .eitem import EItem, HAD_EDGE_BLUE, EItemAnimation +from .vitem import VItem, BLACK from .graphscene import EditGraphScene from .settings import display_setting -from .vitem import BLACK + from . import animations @@ -179,15 +180,33 @@ def add_vert(self, x: float, y: float, edges: list[EItem]) -> None: self.play_sound_signal.emit(SFXEnum.THATS_A_SPIDER) self.undo_stack.push(cmd) - def add_edge(self, u: VT, v: VT) -> None: + def add_edge(self, u: VT, v: VT, verts: list[VItem]) -> None: + """Add an edge between vertices u and v. `verts` is a list of VItems that collide with the edge. + """ graph = self.graph_view.graph_scene.g if vertex_is_w(graph.type(u)) and get_w_partner(graph, u) == v: return None if graph.type(u) == VertexType.W_INPUT and len(graph.neighbors(u)) >= 2 or \ graph.type(v) == VertexType.W_INPUT and len(graph.neighbors(v)) >= 2: return None - cmd = AddEdge(self.graph_view, u, v, self._curr_ety) + if not self.snap_vertex_edge or not verts: + cmd = AddEdge(self.graph_view, u, v, self._curr_ety) + self.undo_stack.push(cmd) + return + + ux, uy = graph.row(u), graph.qubit(u) + # Line was drawn from u to v, we want to order vs with the earlier items first. + def dist(vitem: VItem) -> float: + return (graph.row(vitem.v) - ux)**2 + (graph.row(vitem.v) - uy)**2 + verts.sort(key=dist) + vs = [vitem.v for vitem in verts] + pairs = [(u, vs[0])] + for i in range(1, len(vs)): + pairs.append((vs[i-1],vs[i])) + pairs.append((vs[-1],v)) + cmd = AddEdges(self.graph_view, pairs, self._curr_ety) self.undo_stack.push(cmd) + def vert_moved(self, vs: list[tuple[VT, float, float]]) -> None: self.undo_stack.push(MoveNode(self.graph_view, vs)) diff --git a/zxlive/graphscene.py b/zxlive/graphscene.py index 41ccf15..2614494 100644 --- a/zxlive/graphscene.py +++ b/zxlive/graphscene.py @@ -18,7 +18,7 @@ from typing import Optional, Iterator, Iterable from PySide6.QtCore import Qt, Signal, QRectF -from PySide6.QtGui import QBrush, QColor, QTransform +from PySide6.QtGui import QBrush, QColor, QTransform, QPainterPath from PySide6.QtWidgets import QGraphicsScene, QGraphicsSceneMouseEvent, QGraphicsItem from pyzx.graph.base import EdgeType @@ -235,7 +235,7 @@ class EditGraphScene(GraphScene): # Note that we have to set the argument types to `object`, # otherwise it doesn't work for some reason... vertex_added = Signal(object, object, object) # Actual types: float, float, list[EItem] - edge_added = Signal(object, object) # Actual types: VT, VT + edge_added = Signal(object, object, object) # Actual types: VT, VT, list[VItem] # Currently selected edge type for preview when dragging # to add a new edge @@ -304,7 +304,18 @@ def add_vertex(self, e: QGraphicsSceneMouseEvent) -> None: def add_edge(self, e: QGraphicsSceneMouseEvent) -> None: assert self._drag is not None self.removeItem(self._drag) + v1 = self._drag.start + self._drag = None for it in self.items(e.scenePos(), deviceTransform=QTransform()): if isinstance(it, VItem): - self.edge_added.emit(self._drag.start.v, it.v) - self._drag = None + v2 = it + #self.edge_added.emit(self._drag.start.v, it.v) + assert v2 is not None + path = QPainterPath(v1.pos()) + path.lineTo(e.scenePos()) + colliding_verts = [] + for it in self.items(path, Qt.IntersectsItemShape, Qt.DescendingOrder, deviceTransform=QTransform()): + if isinstance(it, VItem) and it not in (v1,v2): + colliding_verts.append(it) + self.edge_added.emit(v1.v,v2.v,colliding_verts) + From f9658664f0a51183c61b75501761e6835a4dc0c5 Mon Sep 17 00:00:00 2001 From: John van de Wetering Date: Thu, 1 Aug 2024 22:24:14 +0200 Subject: [PATCH 07/11] Add animation to affected vertices --- zxlive/editor_base_panel.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/zxlive/editor_base_panel.py b/zxlive/editor_base_panel.py index f6d5a6d..9ac2bd9 100644 --- a/zxlive/editor_base_panel.py +++ b/zxlive/editor_base_panel.py @@ -23,7 +23,7 @@ from .common import VT, GraphT, ToolType, get_data from .dialogs import show_error_msg from .eitem import EItem, HAD_EDGE_BLUE, EItemAnimation -from .vitem import VItem, BLACK +from .vitem import VItem, BLACK, VItemAnimation from .graphscene import EditGraphScene from .settings import display_setting @@ -206,7 +206,11 @@ def dist(vitem: VItem) -> float: pairs.append((vs[-1],v)) cmd = AddEdges(self.graph_view, pairs, self._curr_ety) self.undo_stack.push(cmd) - + group = QParallelAnimationGroup() + for vitem in verts: + anim = animations.scale(vitem,1.0,400,QEasingCurve(QEasingCurve.Type.InCubic),start=1.3) + group.addAnimation(anim) + self.undo_stack.set_anim(group) def vert_moved(self, vs: list[tuple[VT, float, float]]) -> None: self.undo_stack.push(MoveNode(self.graph_view, vs)) From fac3b1a072874cd481c7b1516a299772f6c97f5e Mon Sep 17 00:00:00 2001 From: John van de Wetering Date: Thu, 1 Aug 2024 22:33:24 +0200 Subject: [PATCH 08/11] Added exception for W nodes --- zxlive/editor_base_panel.py | 4 ++++ zxlive/graphscene.py | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/zxlive/editor_base_panel.py b/zxlive/editor_base_panel.py index 9ac2bd9..62759c2 100644 --- a/zxlive/editor_base_panel.py +++ b/zxlive/editor_base_panel.py @@ -189,6 +189,10 @@ def add_edge(self, u: VT, v: VT, verts: list[VItem]) -> None: if graph.type(u) == VertexType.W_INPUT and len(graph.neighbors(u)) >= 2 or \ graph.type(v) == VertexType.W_INPUT and len(graph.neighbors(v)) >= 2: return None + # We will try to connect all the vertices together in order + # First we filter out the vertices that are not compatible with the edge. + verts = [vitem for vitem in verts if not graph.type(vitem.v) == VertexType.W_INPUT] # we will be adding two edges, which is not compatible with W_INPUT + # but first we check if there any vertices that we do want to additionally connect. if not self.snap_vertex_edge or not verts: cmd = AddEdge(self.graph_view, u, v, self._curr_ety) self.undo_stack.push(cmd) diff --git a/zxlive/graphscene.py b/zxlive/graphscene.py index 2614494..2ede0eb 100644 --- a/zxlive/graphscene.py +++ b/zxlive/graphscene.py @@ -309,8 +309,11 @@ def add_edge(self, e: QGraphicsSceneMouseEvent) -> None: for it in self.items(e.scenePos(), deviceTransform=QTransform()): if isinstance(it, VItem): v2 = it + break #self.edge_added.emit(self._drag.start.v, it.v) - assert v2 is not None + else: + e.ignore() + return path = QPainterPath(v1.pos()) path.lineTo(e.scenePos()) colliding_verts = [] From 7f33fcdddce0c7b47b72da30635210b0e5ad0433 Mon Sep 17 00:00:00 2001 From: John van de Wetering Date: Thu, 1 Aug 2024 22:35:25 +0200 Subject: [PATCH 09/11] Update description of tool --- zxlive/editor_base_panel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zxlive/editor_base_panel.py b/zxlive/editor_base_panel.py index 62759c2..115dd0f 100644 --- a/zxlive/editor_base_panel.py +++ b/zxlive/editor_base_panel.py @@ -376,7 +376,7 @@ def toolbar_select_node_edge(parent: EditorBasePanel) -> Iterator[ToolbarSection snap.setChecked(True) #snap.setText("Vertex-edge snap") snap.setIcon(QIcon(get_data("icons/vertex-snap-to-edge.svg"))) - snap.setToolTip("Snap newly added vertices to the edge beneath them (f)") + snap.setToolTip("Snap vertices to the edge beneath them when adding vertices or edges (f)") snap.setShortcut("f") snap.clicked.connect(lambda: parent._snap_vertex_edge_clicked()) yield ToolbarSection(snap) From 2f97594dce494671df2b0652734e9b2686d66909 Mon Sep 17 00:00:00 2001 From: John van de Wetering Date: Fri, 2 Aug 2024 12:25:59 +0200 Subject: [PATCH 10/11] Comments and whitespace --- zxlive/editor_base_panel.py | 1 - zxlive/graphscene.py | 3 +-- zxlive/rewrite_action.py | 3 +-- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/zxlive/editor_base_panel.py b/zxlive/editor_base_panel.py index 115dd0f..b53ba03 100644 --- a/zxlive/editor_base_panel.py +++ b/zxlive/editor_base_panel.py @@ -374,7 +374,6 @@ def toolbar_select_node_edge(parent: EditorBasePanel) -> Iterator[ToolbarSection snap = QToolButton(parent) snap.setCheckable(True) snap.setChecked(True) - #snap.setText("Vertex-edge snap") snap.setIcon(QIcon(get_data("icons/vertex-snap-to-edge.svg"))) snap.setToolTip("Snap vertices to the edge beneath them when adding vertices or edges (f)") snap.setShortcut("f") diff --git a/zxlive/graphscene.py b/zxlive/graphscene.py index 2ede0eb..2c12768 100644 --- a/zxlive/graphscene.py +++ b/zxlive/graphscene.py @@ -310,8 +310,7 @@ def add_edge(self, e: QGraphicsSceneMouseEvent) -> None: if isinstance(it, VItem): v2 = it break - #self.edge_added.emit(self._drag.start.v, it.v) - else: + else: # It wasn't actually dropped on a vertex e.ignore() return path = QPainterPath(v1.pos()) diff --git a/zxlive/rewrite_action.py b/zxlive/rewrite_action.py index f7fb4f9..5d520f0 100644 --- a/zxlive/rewrite_action.py +++ b/zxlive/rewrite_action.py @@ -50,7 +50,7 @@ def from_rewrite_data(cls, d: RewriteData) -> RewriteAction: picture_path = 'custom' elif 'picture' in d: picture_path = d['picture'] - else: + else: picture_path = None return cls( name=d['text'], @@ -107,7 +107,6 @@ def update_active(self, g: GraphT, verts: list[VT], edges: list[ET]) -> None: ) @property - def tooltip(self) -> str: if self.picture_path is None: return self.tooltip_str From 79345707790ad5bb734bd3e6aad2349fd587e6fd Mon Sep 17 00:00:00 2001 From: John van de Wetering Date: Fri, 2 Aug 2024 12:49:29 +0200 Subject: [PATCH 11/11] Fix mypy errors --- zxlive/editor_base_panel.py | 6 ++++-- zxlive/graphscene.py | 12 +++++++----- zxlive/proof_panel.py | 2 +- zxlive/rewrite_action.py | 4 ++-- zxlive/rule_panel.py | 11 ++++++----- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/zxlive/editor_base_panel.py b/zxlive/editor_base_panel.py index b53ba03..a3195c6 100644 --- a/zxlive/editor_base_panel.py +++ b/zxlive/editor_base_panel.py @@ -17,7 +17,7 @@ from zxlive.sfx import SFXEnum from .base_panel import BasePanel, ToolbarSection -from .commands import (AddEdge, AddEdges, AddNode, AddNodeSnapped, AddWNode, ChangeEdgeColor, +from .commands import (BaseCommand, AddEdge, AddEdges, AddNode, AddNodeSnapped, AddWNode, ChangeEdgeColor, ChangeNodeType, ChangePhase, MoveNode, SetGraph, UpdateGraph) from .common import VT, GraphT, ToolType, get_data @@ -154,6 +154,7 @@ def add_vert(self, x: float, y: float, edges: list[EItem]) -> None: """Add a vertex at point (x,y). `edges` is a list of EItems that are underneath the current position. We will try to connect the vertex to an edge. """ + cmd: BaseCommand if self.snap_vertex_edge and edges and self._curr_vty != VertexType.W_OUTPUT: # Trying to snap vertex to an edge for it in edges: @@ -183,6 +184,7 @@ def add_vert(self, x: float, y: float, edges: list[EItem]) -> None: def add_edge(self, u: VT, v: VT, verts: list[VItem]) -> None: """Add an edge between vertices u and v. `verts` is a list of VItems that collide with the edge. """ + cmd: BaseCommand graph = self.graph_view.graph_scene.g if vertex_is_w(graph.type(u)) and get_w_partner(graph, u) == v: return None @@ -201,7 +203,7 @@ def add_edge(self, u: VT, v: VT, verts: list[VItem]) -> None: ux, uy = graph.row(u), graph.qubit(u) # Line was drawn from u to v, we want to order vs with the earlier items first. def dist(vitem: VItem) -> float: - return (graph.row(vitem.v) - ux)**2 + (graph.row(vitem.v) - uy)**2 + return (graph.row(vitem.v) - ux)**2 + (graph.qubit(vitem.v) - uy)**2 # type: ignore verts.sort(key=dist) vs = [vitem.v for vitem in verts] pairs = [(u, vs[0])] diff --git a/zxlive/graphscene.py b/zxlive/graphscene.py index 2c12768..3b5ebf9 100644 --- a/zxlive/graphscene.py +++ b/zxlive/graphscene.py @@ -24,6 +24,8 @@ from pyzx.graph.base import EdgeType from pyzx.graph import GraphDiff + + from .common import SCALE, VT, ET, GraphT, ToolType, pos_from_view, OFFSET_X, OFFSET_Y from .vitem import VItem from .eitem import EItem, EDragItem @@ -111,8 +113,8 @@ def update_graph(self, new: GraphT, select_new: bool = False) -> None: v_item = self.vertex_map[v] if v_item.phase_item: self.removeItem(v_item.phase_item) - for anim in v_item.active_animations.copy(): - anim.stop() + for anim_v in v_item.active_animations.copy(): + anim_v.stop() selected_vertices.discard(v) self.removeItem(v_item) @@ -121,8 +123,8 @@ def update_graph(self, new: GraphT, select_new: bool = False) -> None: e_item = self.edge_map[e][edge_idx] if e_item.selection_node: self.removeItem(e_item.selection_node) - for anim in e_item.active_animations.copy(): - anim.stop() + for anim_e in e_item.active_animations.copy(): + anim_e.stop() self.removeItem(e_item) self.edge_map[e].pop(edge_idx) s, t = self.g.edge_st(e) @@ -316,7 +318,7 @@ def add_edge(self, e: QGraphicsSceneMouseEvent) -> None: path = QPainterPath(v1.pos()) path.lineTo(e.scenePos()) colliding_verts = [] - for it in self.items(path, Qt.IntersectsItemShape, Qt.DescendingOrder, deviceTransform=QTransform()): + for it in self.items(path, Qt.ItemSelectionMode.IntersectsItemShape, Qt.SortOrder.DescendingOrder, deviceTransform=QTransform()): if isinstance(it, VItem) and it not in (v1,v2): colliding_verts.append(it) self.edge_added.emit(v1.v,v2.v,colliding_verts) diff --git a/zxlive/proof_panel.py b/zxlive/proof_panel.py index 662b296..e6d7ae2 100644 --- a/zxlive/proof_panel.py +++ b/zxlive/proof_panel.py @@ -180,7 +180,7 @@ def _wand_trace_finished(self, trace: WandTrace) -> None: def _magic_hopf(self, trace: WandTrace) -> bool: if not all(isinstance(item, EItem) for item in trace.hit): return False - edges = [item.e for item in trace.hit] + edges: list[ET] = [item.e for item in trace.hit] # type: ignore # We know that the type of `item` is `EItem` because of the check above if not all(edge == edges[0] for edge in edges): return False source, target = self.graph.edge_st(edges[0]) diff --git a/zxlive/rewrite_action.py b/zxlive/rewrite_action.py index 5d520f0..9580b80 100644 --- a/zxlive/rewrite_action.py +++ b/zxlive/rewrite_action.py @@ -145,14 +145,14 @@ def tooltip(self) -> str: buffer = QBuffer() buffer.open(QIODevice.OpenModeFlag.WriteOnly) pixmap.save(buffer, "PNG", quality=100) - image = bytes(buffer.data().toBase64()).decode() + image = bytes(buffer.data().toBase64()).decode() # type: ignore # This gives an overloading error, but QByteArray can be converted to bytes else: pixmap = QPixmap() pixmap.load(get_data("tooltips/"+self.picture_path)) buffer = QBuffer() buffer.open(QIODevice.OpenModeFlag.WriteOnly) pixmap.save(buffer, "PNG", quality=100) - image = bytes(buffer.data().toBase64()).decode() + image = bytes(buffer.data().toBase64()).decode() #type: ignore # This gives an overloading error, but QByteArray can be converted to bytes self.tooltip_str = ''.format(image) + self.tooltip_str self.picture_path = None return self.tooltip_str diff --git a/zxlive/rule_panel.py b/zxlive/rule_panel.py index dbc4cd6..d78c223 100644 --- a/zxlive/rule_panel.py +++ b/zxlive/rule_panel.py @@ -15,7 +15,8 @@ from .editor_base_panel import EditorBasePanel from .graphscene import EditGraphScene from .graphview import RuleEditGraphView - +from .eitem import EItem +from .vitem import VItem class RulePanel(EditorBasePanel): """Panel for the Rule editor of ZXLive.""" @@ -87,12 +88,12 @@ def vert_moved(self, vs: list[tuple[VT, float, float]]) -> None: super().vert_moved(vs) self.update_io_labels(self.graph_scene) - def add_vert(self, x: float, y: float) -> None: - super().add_vert(x, y) + def add_vert(self, x: float, y: float, edges: list[EItem]) -> None: + super().add_vert(x, y, edges) self.update_io_labels(self.graph_scene) - def add_edge(self, u: VT, v: VT) -> None: - super().add_edge(u, v) + def add_edge(self, u: VT, v: VT, verts: list[VItem]) -> None: + super().add_edge(u, v, verts) self.update_io_labels(self.graph_scene) def update_io_labels(self, scene: EditGraphScene) -> None: