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 54fa04e..68bc633 100644
--- a/zxlive/commands.py
+++ b/zxlive/commands.py
@@ -209,6 +209,48 @@ 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)
+ 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."""
@@ -245,7 +287,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 82627a1..d04bc60 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, QEasingCurve, QParallelAnimationGroup
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,15 +17,17 @@
from zxlive.sfx import SFXEnum
from .base_panel import BasePanel, ToolbarSection
-from .commands import (AddEdge, AddNode, 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
from .dialogs import show_error_msg
-from .eitem import HAD_EDGE_BLUE
+from .eitem import EItem, HAD_EDGE_BLUE, EItemAnimation
+from .vitem import VItem, BLACK, VItemAnimation
from .graphscene import EditGraphScene
from .settings import display_setting
-from .vitem import BLACK
+
+from . import animations
class ShapeType(Enum):
@@ -67,6 +69,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 +77,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 +101,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
@@ -143,21 +149,73 @@ 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:
+ 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:
+ 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)
+ 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)
+ else AddNode(self.graph_view, x, y, self._curr_vty)
+
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.
+ """
+ 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
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)
+ # 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)
+ 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.qubit(vitem.v) - uy)**2 # type: ignore
+ 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)
+ 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))
@@ -288,7 +346,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)
@@ -312,7 +370,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.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")
+ snap.clicked.connect(lambda: parent._snap_vertex_edge_clicked())
+ yield ToolbarSection(snap)
def create_list_widget(parent: EditorBasePanel,
diff --git a/zxlive/eitem.py b/zxlive/eitem.py
index b2a72e1..ac86747 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,68 @@ 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 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 118b74c..3b5ebf9 100644
--- a/zxlive/graphscene.py
+++ b/zxlive/graphscene.py
@@ -17,16 +17,19 @@
from typing import Optional, Iterator, Iterable
-from PySide6.QtCore import Qt, Signal
-from PySide6.QtGui import QBrush, QColor, QTransform
+from PySide6.QtCore import Qt, Signal, QRectF
+from PySide6.QtGui import QBrush, QColor, QTransform, QPainterPath
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):
@@ -110,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)
@@ -120,6 +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_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)
@@ -231,8 +236,8 @@ 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
- edge_added = Signal(object, object) # Actual types: VT, VT
+ vertex_added = Signal(object, object, object) # Actual types: float, float, list[EItem]
+ 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
@@ -291,12 +296,30 @@ 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()))
+ # 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(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:
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
+ break
+ else: # It wasn't actually dropped on a vertex
+ e.ignore()
+ return
+ path = QPainterPath(v1.pos())
+ path.lineTo(e.scenePos())
+ colliding_verts = []
+ 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/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
diff --git a/zxlive/proof_panel.py b/zxlive/proof_panel.py
index f38dd06..264f880 100644
--- a/zxlive/proof_panel.py
+++ b/zxlive/proof_panel.py
@@ -154,7 +154,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 len(edges) == 0:
return False
if not all(edge == edges[0] for edge in edges):
diff --git a/zxlive/rewrite_action.py b/zxlive/rewrite_action.py
index ea54de8..8fa624b 100644
--- a/zxlive/rewrite_action.py
+++ b/zxlive/rewrite_action.py
@@ -146,14 +146,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: