22from pathlib import Path
33from typing import Optional
44
5- import matplotlib
65import matplotlib .style as mplstyle
76import napari
87from matplotlib .backends .backend_qtagg import ( # type: ignore[attr-defined]
98 FigureCanvasQTAgg ,
109 NavigationToolbar2QT ,
1110)
1211from matplotlib .figure import Figure
12+ from napari .utils .events import Event
13+ from napari .utils .theme import get_theme
1314from qtpy .QtGui import QIcon
1415from qtpy .QtWidgets import QLabel , QVBoxLayout , QWidget
1516
16- from .util import Interval , from_napari_css_get_size_of
17+ from .util import Interval , from_napari_css_get_size_of , style_sheet_from_theme
1718
1819__all__ = ["BaseNapariMPLWidget" , "NapariMPLWidget" , "SingleAxesWidget" ]
1920
20- _CUSTOM_STYLE_PATH = (
21- Path (matplotlib .get_configdir ()) / "napari-matplotlib.mplstyle"
22- )
23-
2421
2522class BaseNapariMPLWidget (QWidget ):
2623 """
@@ -45,18 +42,17 @@ def __init__(
4542 ):
4643 super ().__init__ (parent = parent )
4744 self .viewer = napari_viewer
48- self ._mpl_style_sheet_path : Optional [Path ] = None
45+ self .napari_theme_style_sheet = style_sheet_from_theme (
46+ get_theme (napari_viewer .theme , as_dict = False )
47+ )
4948
5049 # Sets figure.* style
51- with mplstyle .context (self .mpl_style_sheet_path ):
50+ with mplstyle .context (self .napari_theme_style_sheet ):
5251 self .canvas = FigureCanvasQTAgg () # type: ignore[no-untyped-call]
5352
5453 self .canvas .figure .set_layout_engine ("constrained" )
5554 self .toolbar = NapariNavigationToolbar (self .canvas , parent = self )
5655 self ._replace_toolbar_icons ()
57- # callback to update when napari theme changed
58- # TODO: this isn't working completely (see issue #140)
59- # most of our styling respects the theme change but not all
6056 self .viewer .events .theme .connect (self ._on_napari_theme_changed )
6157
6258 self .setLayout (QVBoxLayout ())
@@ -68,24 +64,6 @@ def figure(self) -> Figure:
6864 """Matplotlib figure."""
6965 return self .canvas .figure
7066
71- @property
72- def mpl_style_sheet_path (self ) -> Path :
73- """
74- Path to the set Matplotlib style sheet.
75- """
76- if self ._mpl_style_sheet_path is not None :
77- return self ._mpl_style_sheet_path
78- elif (_CUSTOM_STYLE_PATH ).exists ():
79- return _CUSTOM_STYLE_PATH
80- elif self ._napari_theme_has_light_bg ():
81- return Path (__file__ ).parent / "styles" / "light.mplstyle"
82- else :
83- return Path (__file__ ).parent / "styles" / "dark.mplstyle"
84-
85- @mpl_style_sheet_path .setter
86- def mpl_style_sheet_path (self , path : Path ) -> None :
87- self ._mpl_style_sheet_path = Path (path )
88-
8967 def add_single_axes (self ) -> None :
9068 """
9169 Add a single Axes to the figure.
@@ -94,13 +72,21 @@ def add_single_axes(self) -> None:
9472 """
9573 # Sets axes.* style.
9674 # Does not set any text styling set by axes.* keys
97- with mplstyle .context (self .mpl_style_sheet_path ):
75+ with mplstyle .context (self .napari_theme_style_sheet ):
9876 self .axes = self .figure .add_subplot ()
9977
100- def _on_napari_theme_changed (self ) -> None :
78+ def _on_napari_theme_changed (self , event : Event ) -> None :
10179 """
10280 Called when the napari theme is changed.
81+
82+ Parameters
83+ ----------
84+ event : napari.utils.events.Event
85+ Event that triggered the callback.
10386 """
87+ self .napari_theme_style_sheet = style_sheet_from_theme (
88+ get_theme (event .value , as_dict = False )
89+ )
10490 self ._replace_toolbar_icons ()
10591
10692 def _napari_theme_has_light_bg (self ) -> bool :
@@ -211,15 +197,18 @@ def current_z(self) -> int:
211197 """
212198 return self .viewer .dims .current_step [0 ]
213199
214- def _on_napari_theme_changed (self ) -> None :
200+ def _on_napari_theme_changed (self , event : Event ) -> None :
215201 """Update MPL toolbar and axis styling when `napari.Viewer.theme` is changed.
216202
217- Note:
218- At the moment we only handle the default 'light' and 'dark' napari themes.
203+ Parameters
204+ ----------
205+ event : napari.utils.events.Event
206+ Event that triggered the callback.
219207 """
220- super ()._on_napari_theme_changed ()
221- self .clear ()
222- self .draw ()
208+ super ()._on_napari_theme_changed (event )
209+ # use self._draw instead of self.draw to cope with redraw while there are no
210+ # layers, this makes the self.clear() obsolete
211+ self ._draw ()
223212
224213 def _setup_callbacks (self ) -> None :
225214 """
@@ -252,13 +241,15 @@ def _draw(self) -> None:
252241 """
253242 # Clearing axes sets new defaults, so need to make sure style is applied when
254243 # this happens
255- with mplstyle .context (self .mpl_style_sheet_path ):
244+ with mplstyle .context (self .napari_theme_style_sheet ):
245+ # everything should be done in the style context
256246 self .clear ()
257- if self .n_selected_layers in self .n_layers_input and all (
258- isinstance (layer , self .input_layer_types ) for layer in self .layers
259- ):
260- self .draw ()
261- self .canvas .draw () # type: ignore[no-untyped-call]
247+ if self .n_selected_layers in self .n_layers_input and all (
248+ isinstance (layer , self .input_layer_types )
249+ for layer in self .layers
250+ ):
251+ self .draw ()
252+ self .canvas .draw () # type: ignore[no-untyped-call]
262253
263254 def clear (self ) -> None :
264255 """
@@ -300,7 +291,7 @@ def clear(self) -> None:
300291 """
301292 Clear the axes.
302293 """
303- with mplstyle .context (self .mpl_style_sheet_path ):
294+ with mplstyle .context (self .napari_theme_style_sheet ):
304295 self .axes .clear ()
305296
306297
0 commit comments