Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add toggle to automatically connect new vertex to edge underneath it #337

Merged
merged 13 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions zxlive/animations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand Down
60 changes: 59 additions & 1 deletion zxlive/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down Expand Up @@ -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()


Expand Down
91 changes: 79 additions & 12 deletions zxlive/editor_base_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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):
Expand Down Expand Up @@ -67,14 +69,15 @@ class EditorBasePanel(BasePanel):

_curr_ety: EdgeType
_curr_vty: VertexType
snap_vertex_edge = True

def __init__(self, *actions: QAction) -> None:
super().__init__(*actions)
self._curr_vty = VertexType.Z
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:
Expand All @@ -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

Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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)
Expand All @@ -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,
Expand Down
Loading
Loading