Skip to content

Commit 2e2b53f

Browse files
authored
feat: add spinner when loading frames (#9)
* feat: add spinner when loading * future
1 parent 7b6e48a commit 2e2b53f

File tree

3 files changed

+84
-45
lines changed

3 files changed

+84
-45
lines changed

src/ndv/viewer/_components.py

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
from __future__ import annotations
2+
3+
from enum import Enum
4+
from pathlib import Path
5+
6+
from qtpy.QtCore import QSize
7+
from qtpy.QtGui import QMovie
8+
from qtpy.QtWidgets import QLabel, QPushButton, QWidget
9+
from superqt import QIconifyIcon
10+
11+
SPIN_GIF = str(Path(__file__).parent / "spin.gif")
12+
13+
14+
class DimToggleButton(QPushButton):
15+
def __init__(self, parent: QWidget | None = None):
16+
icn = QIconifyIcon("f7:view-2d", color="#333333")
17+
icn.addKey("f7:view-3d", state=QIconifyIcon.State.On, color="white")
18+
super().__init__(icn, "", parent)
19+
self.setCheckable(True)
20+
self.setChecked(True)
21+
22+
23+
class QSpinner(QLabel):
24+
def __init__(self, parent: QWidget | None = None):
25+
super().__init__(parent)
26+
size = QSize(16, 16)
27+
mov = QMovie(SPIN_GIF, parent=self)
28+
self.setFixedSize(size)
29+
mov.setScaledSize(size)
30+
mov.setSpeed(150)
31+
mov.start()
32+
self.setMovie(mov)
33+
self.hide()
34+
35+
36+
class ChannelMode(str, Enum):
37+
COMPOSITE = "composite"
38+
MONO = "mono"
39+
40+
def __str__(self) -> str:
41+
return self.value
42+
43+
44+
class ChannelModeButton(QPushButton):
45+
def __init__(self, parent: QWidget | None = None):
46+
super().__init__(parent)
47+
self.setCheckable(True)
48+
self.toggled.connect(self.next_mode)
49+
50+
# set minimum width to the width of the larger string 'composite'
51+
self.setMinimumWidth(92) # magic number :/
52+
53+
def next_mode(self) -> None:
54+
if self.isChecked():
55+
self.setMode(ChannelMode.MONO)
56+
else:
57+
self.setMode(ChannelMode.COMPOSITE)
58+
59+
def mode(self) -> ChannelMode:
60+
return ChannelMode.MONO if self.isChecked() else ChannelMode.COMPOSITE
61+
62+
def setMode(self, mode: ChannelMode) -> None:
63+
# we show the name of the next mode, not the current one
64+
other = ChannelMode.COMPOSITE if mode is ChannelMode.MONO else ChannelMode.MONO
65+
self.setText(str(other))
66+
self.setChecked(mode == ChannelMode.MONO)

src/ndv/viewer/_viewer.py

+18-45
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from collections import defaultdict
44
from collections.abc import Iterable, Mapping, Sequence
5-
from enum import Enum
65
from itertools import cycle
76
from typing import TYPE_CHECKING, Literal, cast
87

@@ -12,6 +11,13 @@
1211
from superqt import QCollapsible, QElidingLabel, QIconifyIcon, ensure_main_thread
1312
from superqt.utils import qthrottled, signals_blocked
1413

14+
from ndv.viewer._components import (
15+
ChannelMode,
16+
ChannelModeButton,
17+
DimToggleButton,
18+
QSpinner,
19+
)
20+
1521
from ._backends import get_canvas
1622
from ._data_wrapper import DataWrapper
1723
from ._dims_slider import DimsSliders
@@ -46,48 +52,6 @@
4652
ALL_CHANNELS = slice(None)
4753

4854

49-
class ChannelMode(str, Enum):
50-
COMPOSITE = "composite"
51-
MONO = "mono"
52-
53-
def __str__(self) -> str:
54-
return self.value
55-
56-
57-
class ChannelModeButton(QPushButton):
58-
def __init__(self, parent: QWidget | None = None):
59-
super().__init__(parent)
60-
self.setCheckable(True)
61-
self.toggled.connect(self.next_mode)
62-
63-
# set minimum width to the width of the larger string 'composite'
64-
self.setMinimumWidth(92) # magic number :/
65-
66-
def next_mode(self) -> None:
67-
if self.isChecked():
68-
self.setMode(ChannelMode.MONO)
69-
else:
70-
self.setMode(ChannelMode.COMPOSITE)
71-
72-
def mode(self) -> ChannelMode:
73-
return ChannelMode.MONO if self.isChecked() else ChannelMode.COMPOSITE
74-
75-
def setMode(self, mode: ChannelMode) -> None:
76-
# we show the name of the next mode, not the current one
77-
other = ChannelMode.COMPOSITE if mode is ChannelMode.MONO else ChannelMode.MONO
78-
self.setText(str(other))
79-
self.setChecked(mode == ChannelMode.MONO)
80-
81-
82-
class DimToggleButton(QPushButton):
83-
def __init__(self, parent: QWidget | None = None):
84-
icn = QIconifyIcon("f7:view-2d", color="#333333")
85-
icn.addKey("f7:view-3d", state=QIconifyIcon.State.On, color="white")
86-
super().__init__(icn, "", parent)
87-
self.setCheckable(True)
88-
self.setChecked(True)
89-
90-
9155
class NDViewer(QWidget):
9256
"""A viewer for ND arrays.
9357
@@ -197,6 +161,8 @@ def __init__(
197161

198162
# place to display dataset summary
199163
self._data_info_label = QElidingLabel("", parent=self)
164+
self._progress_spinner = QSpinner(self)
165+
200166
# place to display arbitrary text
201167
self._hover_info_label = QLabel("", self)
202168
# the canvas that displays the images
@@ -232,10 +198,16 @@ def __init__(
232198
btns.addWidget(self._ndims_btn)
233199
btns.addWidget(self._set_range_btn)
234200

201+
info = QHBoxLayout()
202+
info.setContentsMargins(0, 0, 0, 2)
203+
info.setSpacing(0)
204+
info.addWidget(self._data_info_label)
205+
info.addWidget(self._progress_spinner)
206+
235207
layout = QVBoxLayout(self)
236208
layout.setSpacing(2)
237209
layout.setContentsMargins(6, 6, 6, 6)
238-
layout.addWidget(self._data_info_label)
210+
layout.addLayout(info)
239211
layout.addWidget(self._canvas.qwidget(), 1)
240212
layout.addWidget(self._hover_info_label)
241213
layout.addWidget(self._dims_sliders)
@@ -432,6 +404,7 @@ def _update_data_for_index(self, index: Indices) -> None:
432404
raise type(e)(f"Failed to index data with {index}: {e}") from e
433405

434406
f.add_done_callback(self._on_data_slice_ready)
407+
self._progress_spinner.show()
435408

436409
def closeEvent(self, a0: QCloseEvent | None) -> None:
437410
if self._last_future is not None:
@@ -451,7 +424,7 @@ def _on_data_slice_ready(
451424
# because the future has a reference to this widget in its _done_callbacks
452425
# which will prevent the widget from being garbage collected if the future
453426
self._last_future = None
454-
427+
self._progress_spinner.hide()
455428
if future.cancelled():
456429
return
457430

src/ndv/viewer/spin.gif

2.33 KB
Loading

0 commit comments

Comments
 (0)