Skip to content

Add thumbnail preview functionality to proof panel and model#434

Open
axif0 wants to merge 23 commits intozxcalc:masterfrom
axif0:thumbnails
Open

Add thumbnail preview functionality to proof panel and model#434
axif0 wants to merge 23 commits intozxcalc:masterfrom
axif0:thumbnails

Conversation

@axif0
Copy link
Contributor

@axif0 axif0 commented Feb 16, 2026

  • Introduced a toggle button in the ProofPanel for displaying thumbnails of proof steps.
  • Implemented thumbnail rendering logic in the ProofModel, including caching for performance.
  • Updated settings to manage thumbnail visibility and added related methods in DisplaySettings.
  • Adjusted ProofStepView and ProofStepItemDelegate to accommodate thumbnail display.

Closes: #425

thumb

@RazinShaikh
Copy link
Collaborator

Hi @axif0 , thanks for the PR. Great work, this looks really good! I will have a look at the code soon for review.

In the meanwhile, here's one thing I noticed that needs to be fixed. You manage thumbnail visibility using the settings which makes the toggling them a global effect. The problem with this is that if you want thumbnails in one tab but not the other, this is not possible. Also, if you have thumbnails on in one tab but turn it off in a different tab, the first tab now stops displaying them but the UI layout still has spacing for them:

image

@axif0
Copy link
Contributor Author

axif0 commented Feb 17, 2026

Thank you for the suggetions. @RazinShaikh

proff

@RazinShaikh
Copy link
Collaborator

Thanks, that works. There are still a few things which are not quite stable.

  1. It looks like the thumbnails can be arbitrary long. Can you add some reasonable way to limit this?
image
  1. If I change the diagram without using a mouse, for example by selecting vertices and pressing ctrl+arrow or ctr+shift+arrow, the thumbnail does not update.
image
  1. If the height of the diagram is changed, it is updated in the thumbnail but the layout of the list does not update leaving breaks like this:
image

Lastly, this is not a bug but just to make it easier for the user to find, can you put the thumbnail button somewhere closer to the proof step list. Maybe you can move it to the right or something better.

…improve thumbnail rendering, and add a signal for keyboard vertex movement.
@axif0
Copy link
Contributor Author

axif0 commented Feb 17, 2026

Refactored the proof step view to include a thumbnail toggle and improve thumbnail rendering, and add a signal for keyboard vertex movement.

image

@lia-approves
Copy link
Collaborator

I'm not able to run the most recent commit of this PR. I get the error:

File "/Users/liayeh/Documents/zxlive/zxlive/proof.py", line 377, in ProofStepView
def update(self, region: QRegion | QBitmap | QPolygon | QRect, /) -> None: ...
TypeError: unsupported operand type(s) for |: 'Shiboken.ObjectType' and 'Shiboken.ObjectType'

@axif0
Copy link
Contributor Author

axif0 commented Feb 23, 2026

I'm not able to run the most recent commit of this PR. I get the error:

I updated it using typing.Union . I think it is a OS issue. 😅

zxlive/proof.py Outdated
Comment on lines +373 to +380
@overload
def update(self) -> None: ...

@overload
def update(self, region: Union[QRegion, QBitmap, QPolygon, QRect], /) -> None: ...

@overload
def update(self, x: int, y: int, w: int, h: int, /) -> None: ...
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these necessary?

zxlive/proof.py Outdated
super().update(*args)
self._list.viewport().update()

def edit(self, index: QModelIndex) -> None: # type: ignore[override]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems fine for me without the type ignore


def _on_keyboard_vertices_moved(self) -> None:
"""Refresh the thumbnail for the current proof step after keyboard-based vertex moves."""
self.step_view.refresh_current_thumbnail()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I tried moving vertices with my keyboard, and while this function is called, the thumbnail is not updated.

@boldar99
Copy link
Collaborator

I'm not able to run the most recent commit of this PR. I get the error:

I updated it using typing.Union . I think it is a OS issue. 😅

The | notation for typing was introduced in Python 3.10 (https://peps.python.org/pep-0604/), and zxlive supports python versions >=3.9, so that's probably the reason Lia was not able to run this.

@boldar99
Copy link
Collaborator

This is starting to look quite nice. Please clean up to code and I will take another look!

…nsure keyboard vertex moves update the proof model.
@axif0
Copy link
Contributor Author

axif0 commented Feb 26, 2026

@boldar99 @RazinShaikh hello, Is there anything else for improvement?

@axif0 axif0 requested a review from boldar99 February 26, 2026 15:38
zxlive/proof.py Outdated
Comment on lines +343 to +360
# ---- proxy properties so the rest of the codebase keeps working ----
@property
def thumbnails_visible(self) -> bool:
return self._list.thumbnails_visible

@thumbnails_visible.setter
def thumbnails_visible(self, value: bool) -> None:
self._list.thumbnails_visible = value

def model(self) -> 'ProofModel':
return self._list.model()

def setModel(self, model: 'ProofModel') -> None:
self._list.setModel(model)

def currentIndex(self) -> QModelIndex:
return self._list.currentIndex()

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a more natural way to deal with all the functions that are juts calling the same function of self._list? This doesn't feel elegant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

they were just forwarding calls from ProofStepView down to its internal self._list.
instead of it i added -

def __getattr__(self, name: str) -> Any:
    return getattr(self._list, name)

@axif0 axif0 requested a review from boldar99 February 27, 2026 06:35
@axif0
Copy link
Contributor Author

axif0 commented Feb 27, 2026

Anything else I need to do on my side?

@axif0
Copy link
Contributor Author

axif0 commented Feb 28, 2026

Slightly bumped the max render resolution and now pass the raw high-res pixmap directly into painter.drawPixmap() . This lets Qt natively scale the image using all available physical pixels for Retina displays, eliminating the need for any of that complex, buggy resize logic. which solves the issue i think.

@RazinShaikh
Copy link
Collaborator

@lia-approves Can you please check if the blurriness issue is fixed for you now?

@RazinShaikh
Copy link
Collaborator

Copying the same comment as I added in your other pr:

Can you undo all the type hints you changed which aren't relevant to the issue. It's better to have the changes in an individual PR make logical sense and not be about general code improvements. Moreover, we still support python 3.9 for where we can't use type | None instead of Optional[type]. We may want to stop supporting 3.9 and then modernize the type hints but that's something to decide later, not in this PR.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an optional “filmstrip” thumbnail preview of each proof step diagram in the proof steps panel, to help users keep a global view of the proof while navigating/rewriting.

Changes:

  • Added a thumbnail toggle header UI above the proof step list, and updated the list/delegate to render thumbnails and variable row heights.
  • Implemented thumbnail rendering and caching in ProofModel via a custom item data role.
  • Added a keyboard_vertices_moved signal to refresh thumbnails/step graphs after keyboard-based vertex moves.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
zxlive/proof_panel.py Hooks keyboard-move events to update the current proof step graph (for thumbnail refresh).
zxlive/proof.py Adds thumbnail role/rendering + cache in ProofModel; refactors ProofStepView into a widget with a toggle header; updates delegate painting/size hints for thumbnails.
zxlive/graphview.py Emits a new signal after keyboard-based vertex movement.
zxlive/commands.py Updates typing to reflect ProofStepView no longer being a QListView directly.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

zxlive/proof.py Outdated
if pixmap is not None and not pixmap.isNull() and pixmap.devicePixelRatio() != screen_dpr:
graph = model.get_graph(row)
pixmap = render_graph_thumbnail(graph, screen_dpr)
model._thumbnail_cache[row] = pixmap
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The delegate also mutates model._thumbnail_cache directly when re-rendering for a different DPR. This should go through a ProofModel API (e.g. set_thumbnail(row, pixmap) or invalidate_thumbnail(row) + re-fetch) to avoid relying on private attributes from outside the model.

Suggested change
model._thumbnail_cache[row] = pixmap
# Update the model via its public API instead of touching private state.
if hasattr(model, "set_thumbnail"):
model.set_thumbnail(row, pixmap) # type: ignore[call-arg]
else:
# Fallback: use the standard Qt data API if supported by the model.
model.setData(index, pixmap, THUMBNAIL_ROLE)

Copilot uses AI. Check for mistakes.
Comment on lines +179 to +180
if self.graph_scene.selected_vertices:
self.keyboard_vertices_moved.emit()
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

selected_vertices is a generator (see GraphScene.selected_vertices), so if self.graph_scene.selected_vertices: is always truthy and will emit keyboard_vertices_moved even when no vertices are selected. Cache the selection once (e.g., selected = list(self.graph_scene.selected_vertices)) and use that both for the loop and the emptiness check, or check self.graph_scene.selectedItems() instead.

Copilot uses AI. Check for mistakes.
Comment on lines +709 to +716
# Compute the thumbnail height that paint() will actually use for the
# current viewport width so that the allocated row height always matches
# what is painted, preventing gaps or clipping when the panel is narrow.
pixmap = index.data(THUMBNAIL_ROLE)
if pixmap is not None and not pixmap.isNull():
thumb_height = self._compute_thumb_height(parent, pixmap)
else:
thumb_height = MAX_THUMBNAIL_HEIGHT
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sizeHint() calls index.data(THUMBNAIL_ROLE), which triggers synchronous thumbnail rendering during layout. Toggling thumbnails on or resizing the panel will cause Qt to request size hints for many/all rows, potentially freezing the UI on large proofs. Consider making sizeHint() return a cheap conservative height when the pixmap isn't already cached, and generate thumbnails lazily (e.g., on paint or via a background task) and then trigger a relayout for affected rows.

Suggested change
# Compute the thumbnail height that paint() will actually use for the
# current viewport width so that the allocated row height always matches
# what is painted, preventing gaps or clipping when the panel is narrow.
pixmap = index.data(THUMBNAIL_ROLE)
if pixmap is not None and not pixmap.isNull():
thumb_height = self._compute_thumb_height(parent, pixmap)
else:
thumb_height = MAX_THUMBNAIL_HEIGHT
# Return a conservative height when thumbnails are visible without
# touching THUMBNAIL_ROLE, to avoid triggering synchronous thumbnail
# generation during layout. Thumbnails are rendered lazily in paint().
thumb_height = MAX_THUMBNAIL_HEIGHT

Copilot uses AI. Check for mistakes.
Comment on lines +89 to +94
# Render at a higher maximum resolution so that thumbnails remain crisp on
# modern HiDPI (Retina) displays when dynamically scaled down by QPainter.
max_dim = 1500.0
scale = min(max_dim / rect.width(), max_dim / rect.height())
if scale > 2.0:
scale = 2.0
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thumbnail cache is unbounded, and render_graph_thumbnail can create very large pixmaps (e.g. up to max_dim scaled further by device pixel ratio). With many proof steps this can lead to significant memory usage. Consider lowering the maximum render size to something closer to the maximum displayed size, and/or using an LRU/size-bounded cache so old thumbnails are evicted.

Copilot uses AI. Check for mistakes.
Comment on lines +513 to +517
def set_thumbnails_visible(self, visible: bool) -> None:
"""Toggle thumbnail previews in the proof step list."""
self.thumbnails_visible = visible
self.model()._thumbnail_cache.clear()
self.scheduleDelayedItemsLayout()
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_ProofStepListView reaches into self.model()._thumbnail_cache directly. This couples the view to ProofModel internals and makes future refactors harder. Prefer adding a small public method on ProofModel (e.g. clear_thumbnail_cache() / invalidate_thumbnails()) and calling that instead.

Copilot uses AI. Check for mistakes.
@RazinShaikh
Copy link
Collaborator

@axif0 please address copilot's review comments

@axif0 axif0 requested a review from RazinShaikh March 1, 2026 22:28
@axif0
Copy link
Contributor Author

axif0 commented Mar 2, 2026

Is there anything else I can do? 😅 @RazinShaikh

@RazinShaikh
Copy link
Collaborator

The thumbnails are still not rendering properly. The following screenshot shows that the thumbnails do not display the whole diagram.

image

@axif0
Copy link
Contributor Author

axif0 commented Mar 3, 2026

For this Pr, is the coding structure is suitable for future maintenance? I mean we're designing thumbnails, bt I'm a bit afraid of getting it marged. 😅

@RazinShaikh

@RazinShaikh
Copy link
Collaborator

Sorry, I don't understand your question.

@boldar99
Copy link
Collaborator

boldar99 commented Mar 3, 2026

Thanks for participating in the hackathon. As it is now over I will be closing the PR.
If you want to finish this still, feel free to reopen it later.

@boldar99 boldar99 closed this Mar 3, 2026
@axif0
Copy link
Contributor Author

axif0 commented Mar 5, 2026

@boldar99 Hey, I can't find an option to reopen the the PR 😄 I want to finish the issue

@RazinShaikh RazinShaikh reopened this Mar 5, 2026
@RazinShaikh
Copy link
Collaborator

I have reopened it

@axif0
Copy link
Contributor Author

axif0 commented Mar 5, 2026

Please let me know if there any suggestions . @RazinShaikh @boldar99

@RazinShaikh
Copy link
Collaborator

Has the previous issue of thumbnails not showing the full diagram fixed?

@axif0
Copy link
Contributor Author

axif0 commented Mar 6, 2026

Has the previous issue of thumbnails not showing the full diagram fixed?

because of the the coordinate mismatch in render_graph_thumbnail() phys_w, phys_h it showed error. I fixed it already.
@RazinShaikh

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proof step thumbnails like a filmstrip view

5 participants