@@ -38,9 +38,11 @@ class BaseNapariMPLWidget(QWidget):
38
38
39
39
def __init__ (
40
40
self ,
41
+ napari_viewer : napari .Viewer ,
41
42
parent : Optional [QWidget ] = None ,
42
43
):
43
44
super ().__init__ (parent = parent )
45
+ self .viewer = napari_viewer
44
46
45
47
self .canvas = FigureCanvas ()
46
48
@@ -50,6 +52,8 @@ def __init__(
50
52
self .canvas , parent = self
51
53
) # type: ignore[no-untyped-call]
52
54
self ._replace_toolbar_icons ()
55
+ # callback to update when napari theme changed
56
+ self .viewer .events .theme .connect (self ._on_theme_change )
53
57
54
58
self .setLayout (QVBoxLayout ())
55
59
self .layout ().addWidget (self .toolbar )
@@ -69,25 +73,55 @@ def add_single_axes(self) -> None:
69
73
self .axes = self .figure .subplots ()
70
74
self .apply_napari_colorscheme (self .axes )
71
75
72
- @staticmethod
73
- def apply_napari_colorscheme (ax : Axes ) -> None :
76
+ def apply_napari_colorscheme (self , ax : Axes ) -> None :
74
77
"""Apply napari-compatible colorscheme to an Axes."""
78
+ # get the foreground colours from current theme
79
+ theme = napari .utils .theme .get_theme (self .viewer .theme , as_dict = False )
80
+ fg = theme .foreground .as_hex () # fg is a muted contrast to bg
81
+ tx = theme .text .as_hex () # text is high contrast to bg
82
+
75
83
# changing color of axes background to transparent
76
84
ax .set_facecolor ("none" )
77
85
78
86
# changing colors of all axes
79
87
for spine in ax .spines :
80
- ax .spines [spine ].set_color ("white" )
88
+ ax .spines [spine ].set_color (fg )
81
89
82
- ax .xaxis .label .set_color ("white" )
83
- ax .yaxis .label .set_color ("white" )
90
+ ax .xaxis .label .set_color (tx )
91
+ ax .yaxis .label .set_color (tx )
84
92
85
93
# changing colors of axes labels
86
- ax .tick_params (axis = "x" , colors = "white" )
87
- ax .tick_params (axis = "y" , colors = "white" )
94
+ ax .tick_params (axis = "x" , colors = tx )
95
+ ax .tick_params (axis = "y" , colors = tx )
96
+
97
+ def _on_theme_change (self ) -> None :
98
+ """
99
+ Update the MPL toolbar and axis styling when the `napari.Viewer.theme` is changed.
100
+
101
+ Note: At the moment we only recognise the default 'light' and 'dark' napari themes.
102
+ """
103
+ self ._replace_toolbar_icons ()
104
+ if self .figure .gca ():
105
+ self .apply_napari_colorscheme (self .figure .gca ())
106
+
107
+ def _get_path_to_icon (self ) -> Path :
108
+ """
109
+ Get the icons directory (which is theme-dependent).
110
+ """
111
+ # TODO: can make this more robust by doing some RGB tricks to figure out
112
+ # whether white or black icons are going to be more visible given the
113
+ # theme.background
114
+ islight = self .viewer .theme == "light"
115
+ if islight :
116
+ return ICON_ROOT / "black"
117
+ else :
118
+ return ICON_ROOT / "white"
88
119
89
120
def _replace_toolbar_icons (self ) -> None :
90
- # Modify toolbar icons and some tooltips
121
+ """
122
+ Modifies toolbar icons to match the napari theme, and add some tooltips.
123
+ """
124
+ icon_dir = self ._get_path_to_icon ()
91
125
for action in self .toolbar .actions ():
92
126
text = action .text ()
93
127
if text == "Pan" :
@@ -101,7 +135,7 @@ def _replace_toolbar_icons(self) -> None:
101
135
"Click again to deactivate"
102
136
)
103
137
if len (text ) > 0 : # i.e. not a separator item
104
- icon_path = os .path .join (ICON_ROOT , text + ".png" )
138
+ icon_path = os .path .join (icon_dir , text + ".png" )
105
139
action .setIcon (QIcon (icon_path ))
106
140
107
141
@@ -138,9 +172,7 @@ def __init__(
138
172
napari_viewer : napari .viewer .Viewer ,
139
173
parent : Optional [QWidget ] = None ,
140
174
):
141
- super ().__init__ (parent = parent )
142
-
143
- self .viewer = napari_viewer
175
+ super ().__init__ (napari_viewer = napari_viewer , parent = parent )
144
176
self ._setup_callbacks ()
145
177
self .layers : List [napari .layers .Layer ] = []
146
178
@@ -234,22 +266,24 @@ def __init__(self, *args, **kwargs): # type: ignore[no-untyped-def]
234
266
def _update_buttons_checked (self ) -> None :
235
267
"""Update toggle tool icons when selected/unselected."""
236
268
super ()._update_buttons_checked ()
269
+ icon_dir = self .parentWidget ()._get_path_to_icon ()
270
+
237
271
# changes pan/zoom icons depending on state (checked or not)
238
272
if "pan" in self ._actions :
239
273
if self ._actions ["pan" ].isChecked ():
240
274
self ._actions ["pan" ].setIcon (
241
- QIcon (os .path .join (ICON_ROOT , "Pan_checked.png" ))
275
+ QIcon (os .path .join (icon_dir , "Pan_checked.png" ))
242
276
)
243
277
else :
244
278
self ._actions ["pan" ].setIcon (
245
- QIcon (os .path .join (ICON_ROOT , "Pan.png" ))
279
+ QIcon (os .path .join (icon_dir , "Pan.png" ))
246
280
)
247
281
if "zoom" in self ._actions :
248
282
if self ._actions ["zoom" ].isChecked ():
249
283
self ._actions ["zoom" ].setIcon (
250
- QIcon (os .path .join (ICON_ROOT , "Zoom_checked.png" ))
284
+ QIcon (os .path .join (icon_dir , "Zoom_checked.png" ))
251
285
)
252
286
else :
253
287
self ._actions ["zoom" ].setIcon (
254
- QIcon (os .path .join (ICON_ROOT , "Zoom.png" ))
288
+ QIcon (os .path .join (icon_dir , "Zoom.png" ))
255
289
)
0 commit comments