Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
35907c3
Add thumbnail preview functionality to proof panel and model
axif0 Feb 16, 2026
b74fbb9
Refactor thumbnail visibility handling in proof panel and model
axif0 Feb 17, 2026
e6c6e27
feat: Refactor the proof step view to include a thumbnail toggle and …
axif0 Feb 17, 2026
4ccc522
Merge branch 'master' of https://github.com/axif0/zxlive into thumbnails
axif0 Feb 17, 2026
00be501
Enhance type hinting in proof.py
axif0 Feb 18, 2026
60a036a
refactor: Update type hint for `update` method's `region` parameter t…
axif0 Feb 23, 2026
f0fbc22
feat: improve thumbnail rendering and sizing in the proof panel and e…
axif0 Feb 25, 2026
a7f77a5
Merge branch 'master' of https://github.com/axif0/zxlive into thumbnails
axif0 Feb 26, 2026
be9e1d6
Refactor `ProofPanel` to use `__getattr__` for dynamic method delegat…
axif0 Feb 26, 2026
8120d36
Remove unnecessary functions
boldar99 Feb 27, 2026
ef3f62c
Modernize type hints and improve graph thumbnail rendering
axif0 Feb 27, 2026
bcc5f7e
fix lint error
axif0 Feb 27, 2026
484879f
Debounce thumbnail re-rendering on resize events using QTimer to prev…
axif0 Feb 27, 2026
4c637cb
Fix HiDPI thumbnail blurriness natively without excessive scaling com…
axif0 Feb 28, 2026
ef5b76a
Merge branch 'thumbnails' but keep simple, working HiDPI fix instead …
axif0 Feb 28, 2026
ca47c43
Fix mypy warning
axif0 Feb 28, 2026
5879f6e
undo type hint changes of mine
axif0 Mar 1, 2026
28c11ee
Add a setting for default proof step thumbnail visibility and improve…
axif0 Mar 1, 2026
c587921
fixed lint error
axif0 Mar 1, 2026
6a07fce
adjust whitespace and comment spacing
axif0 Mar 1, 2026
6db6c81
Correct scene rendering dimensions when rendering to pixmap.
axif0 Mar 5, 2026
e772eba
Merge branch 'master' of https://github.com/zxcalc/zxlive into thumbn…
axif0 Mar 5, 2026
0215a57
reduce max complexity
axif0 Mar 5, 2026
293d019
add "Show thumbnails" View menu action
RazinShaikh Mar 23, 2026
16776fa
Merge branch 'master' of https://github.com/zxcalc/zxlive into thumbn…
axif0 Mar 25, 2026
144df58
Merge branch 'master' of https://github.com/zxcalc/zxlive into thumbn…
axif0 Apr 1, 2026
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
3 changes: 1 addition & 2 deletions zxlive/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

from PySide6.QtCore import QModelIndex
from PySide6.QtGui import QUndoCommand
from PySide6.QtWidgets import QListView
from pyzx.graph.diff import GraphDiff
from pyzx.symbolic import Poly
from pyzx.utils import EdgeType, VertexType, get_w_partner, vertex_is_w, get_w_io, get_z_box_label, set_z_box_label
Expand Down Expand Up @@ -445,7 +444,7 @@ class AddRewriteStep(UpdateGraph):
The rewrite is inserted after the currently selected step. In particular, it
replaces all rewrites that were previously after the current selection.
"""
step_view: QListView
step_view: ProofStepView
name: str
diff: Optional[GraphDiff] = None

Expand Down
57 changes: 33 additions & 24 deletions zxlive/graphview.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ def __init__(self, start: QPointF, shift: bool = False) -> None:

GRID_SCALE = SCALE / 2

# Maps arrow keys to (qubit_delta, row_delta) offsets for vertex movement.
_ARROW_OFFSETS: dict[int, tuple[int, int]] = {
Qt.Key.Key_Up: (-1, 0),
Qt.Key.Key_Down: ( 1, 0),
Qt.Key.Key_Left: ( 0, -1),
Qt.Key.Key_Right: ( 0, 1),
}


class GraphView(QGraphicsView):
"""QtWidget containing a graph
Expand All @@ -74,6 +82,7 @@ class GraphView(QGraphicsView):

wand_trace_finished = Signal(object)
merge_triggered = Signal()
keyboard_vertices_moved = Signal()
draw_background_lines = True

def __init__(self, graph_scene: GraphScene) -> None:
Expand Down Expand Up @@ -152,31 +161,31 @@ def mousePressEvent(self, e: QMouseEvent) -> None:

def keyPressEvent(self, e: QKeyEvent) -> None:
"""Logic for moving selected vertices with arrow keys and merging selected vertices with Ctrl+M"""
if Qt.KeyboardModifier.ControlModifier & e.modifiers():
g = self.graph_scene.g
if Qt.KeyboardModifier.ShiftModifier & e.modifiers():
distance = 1 / get_settings_value("snap-granularity", int)
else:
distance = 0.5
if e.key() == Qt.Key.Key_M:
# Merge vertices at the same position
self.merge_triggered.emit()
return
for v in self.graph_scene.selected_vertices:
vitem = self.graph_scene.vertex_map[v]
x = g.row(v)
y = g.qubit(v)
if e.key() == Qt.Key.Key_Up:
g.set_position(v, y - distance, x)
elif e.key() == Qt.Key.Key_Down:
g.set_position(v, y + distance, x)
elif e.key() == Qt.Key.Key_Left:
g.set_position(v, y, x - distance)
elif e.key() == Qt.Key.Key_Right:
g.set_position(v, y, x + distance)
vitem.set_pos_from_graph()
else:
if not (Qt.KeyboardModifier.ControlModifier & e.modifiers()):
super().keyPressEvent(e)
return

if e.key() == Qt.Key.Key_M:
self.merge_triggered.emit()
return

offsets = _ARROW_OFFSETS.get(e.key())
if offsets is None:
return

g = self.graph_scene.g
if Qt.KeyboardModifier.ShiftModifier & e.modifiers():
distance = 1 / get_settings_value("snap-granularity", int)
else:
distance = 0.5

dy, dx = offsets
selected = list(self.graph_scene.selected_vertices)
for v in selected:
g.set_position(v, g.qubit(v) + dy * distance, g.row(v) + dx * distance)
self.graph_scene.vertex_map[v].set_pos_from_graph()
if selected:
self.keyboard_vertices_moved.emit()

# TODO: Fix code complexity
# noqa: complexipy
Expand Down
32 changes: 31 additions & 1 deletion zxlive/mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,11 +224,17 @@ def native_shortcut(key: QKeySequence.StandardKey) -> str:
self.fit_view_action = self._new_action(
"Fit view", self.fit_view, QKeySequence("C"),
"Fits the view to the diagram")
self.show_thumbnails_action = self._new_action(
"Show thumbnails", self.toggle_thumbnails, QKeySequence("t"),
"Toggle proof step diagram previews", checkable=True)
self.show_thumbnails_action.setChecked(get_settings_value("proof/show-thumbnails", bool))

view_menu = menu.addMenu("&View")
view_menu.addAction(self.zoom_in_action)
view_menu.addAction(self.zoom_out_action)
view_menu.addAction(self.fit_view_action)
view_menu.addSeparator()
view_menu.addAction(self.show_thumbnails_action)

new_rewrite_from_file = self._new_action(
"New rewrite from file", lambda: create_new_rewrite(self),
Expand Down Expand Up @@ -290,6 +296,9 @@ def _reset_menus(self, has_active_tab: bool) -> None:
# Export to tikz and gif are enabled only if there is a proof in the active tab.
self.export_tikz_proof.setEnabled(has_active_tab and isinstance(self.active_panel, ProofPanel))
self.export_gif_proof.setEnabled(has_active_tab and isinstance(self.active_panel, ProofPanel))
self.show_thumbnails_action.setEnabled(has_active_tab and isinstance(self.active_panel, ProofPanel))
if has_active_tab and isinstance(self.active_panel, ProofPanel):
self.show_thumbnails_action.setChecked(self.active_panel.step_view.thumbnails_visible)

# Paste is enabled only if there is something in the clipboard.
self.paste_action.setEnabled(has_active_tab and self._has_pasteable_clipboard_data())
Expand All @@ -306,10 +315,13 @@ def _new_action(
self, name: str, trigger: Callable,
shortcut: QKeySequence | QKeySequence.StandardKey | None,
tooltip: str, icon_file: Optional[str] = None,
alt_shortcut: Optional[QKeySequence | QKeySequence.StandardKey] = None
alt_shortcut: Optional[QKeySequence | QKeySequence.StandardKey] = None,
checkable: bool = False
) -> QAction:
assert not alt_shortcut or shortcut
action = QAction(name, self)
if checkable:
action.setCheckable(True)
if icon_file:
action.setIcon(QIcon(get_data(f"icons/{icon_file}")))
action.setToolTip(tooltip)
Expand Down Expand Up @@ -514,8 +526,11 @@ def update_tab_name(self, clean: bool) -> None:
def tab_changed(self, i: int) -> None:
if isinstance(self.active_panel, ProofPanel):
self.proof_as_rewrite_action.setEnabled(True)
self.show_thumbnails_action.setEnabled(True)
self.show_thumbnails_action.setChecked(self.active_panel.step_view.thumbnails_visible)
else:
self.proof_as_rewrite_action.setEnabled(False)
self.show_thumbnails_action.setEnabled(False)
self._undo_changed()
self._redo_changed()
if self.active_panel:
Expand Down Expand Up @@ -763,6 +778,14 @@ def _new_panel(self, panel: BasePanel, name: str) -> None:
panel.undo_stack.canRedoChanged.connect(self._redo_changed)
panel.play_sound_signal.connect(self.play_sound)
panel.undo_stack.indexChanged.connect(self._auto_save_if_needed)
if isinstance(panel, ProofPanel):
panel.step_view.thumbnails_toggled.connect(self._on_proof_thumbnails_toggled)

def _on_proof_thumbnails_toggled(self, checked: bool) -> None:
# Check if the signal came from the currently active panel
if hasattr(self, 'active_panel') and isinstance(self.active_panel, ProofPanel):
if self.sender() is self.active_panel.step_view:
self.show_thumbnails_action.setChecked(checked)

def _auto_save_if_needed(self) -> None:
panel = self.active_panel
Expand Down Expand Up @@ -984,6 +1007,13 @@ def update_font(self) -> None:
w = cast(BasePanel, self.tab_widget.widget(i))
w.update_font()

def toggle_thumbnails(self) -> None:
"""Toggle the show thumbnails setting from the View menu."""
checked = self.show_thumbnails_action.isChecked()
# update current proof panel
if isinstance(self.active_panel, ProofPanel):
self.active_panel.step_view.thumbnails_visible = checked

def toggle_auto_save(self) -> None:
"""Toggle the auto-save setting from the File menu."""
from .common import set_settings_value
Expand Down
Loading
Loading