diff --git a/zxlive/base_panel.py b/zxlive/base_panel.py index 34baf248..805bd316 100644 --- a/zxlive/base_panel.py +++ b/zxlive/base_panel.py @@ -3,6 +3,7 @@ from dataclasses import dataclass from typing import Iterator, Optional, Sequence +from PySide6.QtCore import Signal from PySide6.QtGui import QAction from PySide6.QtWidgets import (QAbstractButton, QButtonGroup, QSplitter, QToolBar, QVBoxLayout, QWidget) @@ -42,6 +43,8 @@ class BasePanel(QWidget): file_path: Optional[str] file_type: Optional[FileFormat] + play_sound_signal = Signal(object) # Actual type: SFXEnum + def __init__(self, *actions: QAction) -> None: super().__init__() self.addActions(actions) diff --git a/zxlive/edit_panel.py b/zxlive/edit_panel.py index e2bc2445..2a668671 100644 --- a/zxlive/edit_panel.py +++ b/zxlive/edit_panel.py @@ -46,7 +46,6 @@ def __init__(self, graph: GraphT, *actions: QAction) -> None: self.create_side_bar() self.splitter.addWidget(self.sidebar) - def _toolbar_sections(self) -> Iterator[ToolbarSection]: yield from super()._toolbar_sections() @@ -60,7 +59,6 @@ def _toolbar_sections(self) -> Iterator[ToolbarSection]: self.start_derivation.clicked.connect(self._start_derivation) yield ToolbarSection(self.start_derivation) - def _start_derivation(self) -> None: if not self.graph_scene.g.is_well_formed(): show_error_msg("Graph is not well-formed", parent=self) diff --git a/zxlive/editor_base_panel.py b/zxlive/editor_base_panel.py index 048ed10f..6c54e740 100644 --- a/zxlive/editor_base_panel.py +++ b/zxlive/editor_base_panel.py @@ -14,6 +14,7 @@ from pyzx import EdgeType, VertexType from pyzx.utils import get_w_partner, vertex_is_w from pyzx.graph.jsonparser import string_to_phase +from zxlive.sfx import SFXEnum from .base_panel import BasePanel, ToolbarSection from .commands import (AddEdge, AddNode, AddWNode, ChangeEdgeColor, @@ -144,6 +145,7 @@ def delete_selection(self) -> None: 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) + self.play_sound_signal.emit(SFXEnum.THATS_A_SPIDER) self.undo_stack.push(cmd) def add_edge(self, u: VT, v: VT) -> None: diff --git a/zxlive/mainwindow.py b/zxlive/mainwindow.py index 2ce6b86e..f187ff53 100644 --- a/zxlive/mainwindow.py +++ b/zxlive/mainwindow.py @@ -16,11 +16,13 @@ from __future__ import annotations import copy +import random from typing import Callable, Optional, cast -from PySide6.QtCore import (QByteArray, QEvent, QFile, QFileInfo, QIODevice, - QSettings, QTextStream, Qt) -from PySide6.QtGui import QAction, QCloseEvent, QIcon, QKeySequence +from PySide6.QtCore import (QByteArray, QDir, QEvent, QFile, QFileInfo, + QIODevice, QSettings, QTextStream, Qt, QUrl) +from PySide6.QtGui import QAction, QCloseEvent, QIcon, QKeySequence, QShortcut +from PySide6.QtMultimedia import QSoundEffect from PySide6.QtWidgets import (QDialog, QMainWindow, QMessageBox, QTableWidget, QTableWidgetItem, QTabWidget, QVBoxLayout, QWidget) @@ -42,6 +44,7 @@ from .edit_panel import GraphEditPanel from .proof_panel import ProofPanel from .rule_panel import RulePanel +from .sfx import SFXEnum from .tikz import proof_to_tikz @@ -165,6 +168,12 @@ def __init__(self) -> None: menu.setStyleSheet("QMenu::item:disabled { color: gray }") self._reset_menus(False) + self.effect = QSoundEffect() + self.effect.setLoopCount(1) + + self.sfx_on = False + QShortcut(QKeySequence("Ctrl+B"), self).activated.connect(self._toggle_sfx) + def open_demo_graph(self) -> None: graph = construct_circuit() self.new_graph(graph) @@ -362,6 +371,8 @@ def handle_save_file_action(self) -> bool: out << data file.close() self.active_panel.undo_stack.setClean() + if random.random() < 0.1: + self.play_sound(SFXEnum.IRANIAN_BUS) return True @@ -444,6 +455,7 @@ def _new_panel(self, panel: BasePanel, name: str) -> None: panel.undo_stack.cleanChanged.connect(self.update_tab_name) panel.undo_stack.canUndoChanged.connect(self._undo_changed) panel.undo_stack.canRedoChanged.connect(self._redo_changed) + panel.play_sound_signal.connect(self.play_sound) def new_graph(self, graph: Optional[GraphT] = None, name: Optional[str] = None) -> None: _graph = graph or GraphT() @@ -562,3 +574,18 @@ def proof_as_lemma(self) -> None: def update_colors(self) -> None: if self.active_panel is not None: self.active_panel.update_colors() + + def play_sound(self, s: SFXEnum) -> None: + if self.sfx_on: + fullpath = QDir.current().absoluteFilePath(s.value) + url = QUrl.fromLocalFile(fullpath) + self.effect.setSource(url) + self.effect.play() + + def _toggle_sfx(self) -> None: + self.sfx_on = not self.sfx_on + if self.sfx_on: + self.play_sound(random.choice([ + SFXEnum.WELCOME_EVERYBODY, + SFXEnum.OK_IM_GONNA_START, + ])) \ No newline at end of file diff --git a/zxlive/proof_panel.py b/zxlive/proof_panel.py index 12107fe6..e01dba3c 100644 --- a/zxlive/proof_panel.py +++ b/zxlive/proof_panel.py @@ -30,6 +30,7 @@ from .editor_base_panel import string_to_complex from .rewrite_data import action_groups, refresh_custom_rules from .rewrite_action import RewriteActionTreeModel +from .sfx import SFXEnum class ProofPanel(BasePanel): @@ -182,11 +183,13 @@ def _vertex_dropped_onto(self, v: VT, w: VT) -> None: pyzx.basicrules.fuse(g, w, v) anim = anims.fuse(self.graph_scene.vertex_map[v], self.graph_scene.vertex_map[w]) cmd = AddRewriteStep(self.graph_view, g, self.step_view, "fuse spiders") + self.play_sound_signal.emit(SFXEnum.THATS_SPIDER_FUSION) self.undo_stack.push(cmd, anim_before=anim) elif pyzx.basicrules.check_strong_comp(g, v, w): pyzx.basicrules.strong_comp(g, w, v) anim = anims.strong_comp(self.graph, g, w, self.graph_scene) cmd = AddRewriteStep(self.graph_view, g, self.step_view, "bialgebra") + self.play_sound_signal.emit(SFXEnum.BOOM_BOOM_BOOM) self.undo_stack.push(cmd, anim_after=anim) def _wand_trace_finished(self, trace: WandTrace) -> None: diff --git a/zxlive/sfx.py b/zxlive/sfx.py new file mode 100644 index 00000000..85328cf3 --- /dev/null +++ b/zxlive/sfx.py @@ -0,0 +1,10 @@ +from enum import Enum + +class SFXEnum(Enum): + BOOM_BOOM_BOOM = "zxlive/sfx/boom-boom-boom.wav" + IRANIAN_BUS = "zxlive/sfx/iranian-bus.wav" + OK_IM_GONNA_START = "zxlive/sfx/ok-im-gonna-start.wav" + THATS_A_SPIDER = "zxlive/sfx/thats-a-spider.wav" + THATS_SPIDER_FUSION = "zxlive/sfx/thats-spider-fusion.wav" + THEY_FALL_OFF = "zxlive/sfx/they-fall-off.wav" + WELCOME_EVERYBODY = "zxlive/sfx/welcome-everybody.wav"