diff --git a/docs/changelog.rst b/docs/changelog.rst index 32530ba3..10b949df 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -11,7 +11,7 @@ New features Visual improvements ~~~~~~~~~~~~~~~~~~~ -- The background of ``napari-matplotlib`` figures and axes is now transparent. +- The background of ``napari-matplotlib`` figures and axes is now transparent, and the text and axis colour respects the ``napari`` theme. - The icons in the Matplotlib toolbar are now the same size as icons in the napari window. Changes diff --git a/src/napari_matplotlib/base.py b/src/napari_matplotlib/base.py index 25b10ee7..07873896 100644 --- a/src/napari_matplotlib/base.py +++ b/src/napari_matplotlib/base.py @@ -38,9 +38,11 @@ class BaseNapariMPLWidget(QWidget): def __init__( self, + napari_viewer: napari.Viewer, parent: Optional[QWidget] = None, ): super().__init__(parent=parent) + self.viewer = napari_viewer self.canvas = FigureCanvas() @@ -50,6 +52,10 @@ def __init__( self.canvas, parent=self ) # type: ignore[no-untyped-call] self._replace_toolbar_icons() + # callback to update when napari theme changed + # TODO: this isn't working completely (see issue #140) + # most of our styling respects the theme change but not all + self.viewer.events.theme.connect(self._on_theme_change) self.setLayout(QVBoxLayout()) self.layout().addWidget(self.toolbar) @@ -69,25 +75,64 @@ def add_single_axes(self) -> None: self.axes = self.figure.subplots() self.apply_napari_colorscheme(self.axes) - @staticmethod - def apply_napari_colorscheme(ax: Axes) -> None: + def apply_napari_colorscheme(self, ax: Axes) -> None: """Apply napari-compatible colorscheme to an Axes.""" + # get the foreground colours from current theme + theme = napari.utils.theme.get_theme(self.viewer.theme, as_dict=False) + fg_colour = theme.foreground.as_hex() # fg is a muted contrast to bg + text_colour = theme.text.as_hex() # text is high contrast to bg + # changing color of axes background to transparent ax.set_facecolor("none") # changing colors of all axes for spine in ax.spines: - ax.spines[spine].set_color("white") + ax.spines[spine].set_color(fg_colour) - ax.xaxis.label.set_color("white") - ax.yaxis.label.set_color("white") + ax.xaxis.label.set_color(text_colour) + ax.yaxis.label.set_color(text_colour) # changing colors of axes labels - ax.tick_params(axis="x", colors="white") - ax.tick_params(axis="y", colors="white") + ax.tick_params(axis="x", colors=text_colour) + ax.tick_params(axis="y", colors=text_colour) + + def _on_theme_change(self) -> None: + """Update MPL toolbar and axis styling when `napari.Viewer.theme` is changed. + + Note: + At the moment we only handle the default 'light' and 'dark' napari themes. + """ + self._replace_toolbar_icons() + if self.figure.gca(): + self.apply_napari_colorscheme(self.figure.gca()) + + def _theme_has_light_bg(self) -> bool: + """ + Does this theme have a light background? + + Returns + ------- + bool + True if theme's background colour has hsl lighter than 50%, False if darker. + """ + theme = napari.utils.theme.get_theme(self.viewer.theme, as_dict=False) + _, _, bg_lightness = theme.background.as_hsl_tuple() + return bg_lightness > 0.5 + + def _get_path_to_icon(self) -> Path: + """ + Get the icons directory (which is theme-dependent). + """ + if self._theme_has_light_bg(): + return ICON_ROOT / "black" + else: + return ICON_ROOT / "white" def _replace_toolbar_icons(self) -> None: - # Modify toolbar icons and some tooltips + """ + Modifies toolbar icons to match the napari theme, and add some tooltips. + """ + icon_dir = self._get_path_to_icon() for action in self.toolbar.actions(): text = action.text() if text == "Pan": @@ -101,7 +146,7 @@ def _replace_toolbar_icons(self) -> None: "Click again to deactivate" ) if len(text) > 0: # i.e. not a separator item - icon_path = os.path.join(ICON_ROOT, text + ".png") + icon_path = os.path.join(icon_dir, text + ".png") action.setIcon(QIcon(icon_path)) @@ -138,9 +183,7 @@ def __init__( napari_viewer: napari.viewer.Viewer, parent: Optional[QWidget] = None, ): - super().__init__(parent=parent) - - self.viewer = napari_viewer + super().__init__(napari_viewer=napari_viewer, parent=parent) self._setup_callbacks() self.layers: List[napari.layers.Layer] = [] @@ -235,22 +278,24 @@ def __init__(self, *args, **kwargs): # type: ignore[no-untyped-def] def _update_buttons_checked(self) -> None: """Update toggle tool icons when selected/unselected.""" super()._update_buttons_checked() + icon_dir = self.parentWidget()._get_path_to_icon() + # changes pan/zoom icons depending on state (checked or not) if "pan" in self._actions: if self._actions["pan"].isChecked(): self._actions["pan"].setIcon( - QIcon(os.path.join(ICON_ROOT, "Pan_checked.png")) + QIcon(os.path.join(icon_dir, "Pan_checked.png")) ) else: self._actions["pan"].setIcon( - QIcon(os.path.join(ICON_ROOT, "Pan.png")) + QIcon(os.path.join(icon_dir, "Pan.png")) ) if "zoom" in self._actions: if self._actions["zoom"].isChecked(): self._actions["zoom"].setIcon( - QIcon(os.path.join(ICON_ROOT, "Zoom_checked.png")) + QIcon(os.path.join(icon_dir, "Zoom_checked.png")) ) else: self._actions["zoom"].setIcon( - QIcon(os.path.join(ICON_ROOT, "Zoom.png")) + QIcon(os.path.join(icon_dir, "Zoom.png")) ) diff --git a/src/napari_matplotlib/icons/black/Back.png b/src/napari_matplotlib/icons/black/Back.png new file mode 100644 index 00000000..d7c65b43 Binary files /dev/null and b/src/napari_matplotlib/icons/black/Back.png differ diff --git a/src/napari_matplotlib/icons/black/Customize.png b/src/napari_matplotlib/icons/black/Customize.png new file mode 100644 index 00000000..9f56bb6d Binary files /dev/null and b/src/napari_matplotlib/icons/black/Customize.png differ diff --git a/src/napari_matplotlib/icons/black/Forward.png b/src/napari_matplotlib/icons/black/Forward.png new file mode 100644 index 00000000..52770f6f Binary files /dev/null and b/src/napari_matplotlib/icons/black/Forward.png differ diff --git a/src/napari_matplotlib/icons/black/Home.png b/src/napari_matplotlib/icons/black/Home.png new file mode 100644 index 00000000..9e527bfd Binary files /dev/null and b/src/napari_matplotlib/icons/black/Home.png differ diff --git a/src/napari_matplotlib/icons/black/Pan.png b/src/napari_matplotlib/icons/black/Pan.png new file mode 100644 index 00000000..36332c34 Binary files /dev/null and b/src/napari_matplotlib/icons/black/Pan.png differ diff --git a/src/napari_matplotlib/icons/black/Pan_checked.png b/src/napari_matplotlib/icons/black/Pan_checked.png new file mode 100644 index 00000000..eb0b908f Binary files /dev/null and b/src/napari_matplotlib/icons/black/Pan_checked.png differ diff --git a/src/napari_matplotlib/icons/black/Save.png b/src/napari_matplotlib/icons/black/Save.png new file mode 100644 index 00000000..79b0d030 Binary files /dev/null and b/src/napari_matplotlib/icons/black/Save.png differ diff --git a/src/napari_matplotlib/icons/black/Subplots.png b/src/napari_matplotlib/icons/black/Subplots.png new file mode 100644 index 00000000..aa15d760 Binary files /dev/null and b/src/napari_matplotlib/icons/black/Subplots.png differ diff --git a/src/napari_matplotlib/icons/black/Zoom.png b/src/napari_matplotlib/icons/black/Zoom.png new file mode 100644 index 00000000..4d2898b7 Binary files /dev/null and b/src/napari_matplotlib/icons/black/Zoom.png differ diff --git a/src/napari_matplotlib/icons/black/Zoom_checked.png b/src/napari_matplotlib/icons/black/Zoom_checked.png new file mode 100644 index 00000000..ad769e66 Binary files /dev/null and b/src/napari_matplotlib/icons/black/Zoom_checked.png differ diff --git a/src/napari_matplotlib/icons/Back.png b/src/napari_matplotlib/icons/white/Back.png similarity index 100% rename from src/napari_matplotlib/icons/Back.png rename to src/napari_matplotlib/icons/white/Back.png diff --git a/src/napari_matplotlib/icons/Customize.png b/src/napari_matplotlib/icons/white/Customize.png similarity index 100% rename from src/napari_matplotlib/icons/Customize.png rename to src/napari_matplotlib/icons/white/Customize.png diff --git a/src/napari_matplotlib/icons/Forward.png b/src/napari_matplotlib/icons/white/Forward.png similarity index 100% rename from src/napari_matplotlib/icons/Forward.png rename to src/napari_matplotlib/icons/white/Forward.png diff --git a/src/napari_matplotlib/icons/Home.png b/src/napari_matplotlib/icons/white/Home.png similarity index 100% rename from src/napari_matplotlib/icons/Home.png rename to src/napari_matplotlib/icons/white/Home.png diff --git a/src/napari_matplotlib/icons/Pan.png b/src/napari_matplotlib/icons/white/Pan.png similarity index 100% rename from src/napari_matplotlib/icons/Pan.png rename to src/napari_matplotlib/icons/white/Pan.png diff --git a/src/napari_matplotlib/icons/Pan_checked.png b/src/napari_matplotlib/icons/white/Pan_checked.png similarity index 100% rename from src/napari_matplotlib/icons/Pan_checked.png rename to src/napari_matplotlib/icons/white/Pan_checked.png diff --git a/src/napari_matplotlib/icons/Save.png b/src/napari_matplotlib/icons/white/Save.png similarity index 100% rename from src/napari_matplotlib/icons/Save.png rename to src/napari_matplotlib/icons/white/Save.png diff --git a/src/napari_matplotlib/icons/Subplots.png b/src/napari_matplotlib/icons/white/Subplots.png similarity index 100% rename from src/napari_matplotlib/icons/Subplots.png rename to src/napari_matplotlib/icons/white/Subplots.png diff --git a/src/napari_matplotlib/icons/Zoom.png b/src/napari_matplotlib/icons/white/Zoom.png similarity index 100% rename from src/napari_matplotlib/icons/Zoom.png rename to src/napari_matplotlib/icons/white/Zoom.png diff --git a/src/napari_matplotlib/icons/Zoom_checked.png b/src/napari_matplotlib/icons/white/Zoom_checked.png similarity index 100% rename from src/napari_matplotlib/icons/Zoom_checked.png rename to src/napari_matplotlib/icons/white/Zoom_checked.png diff --git a/src/napari_matplotlib/tests/baseline/test_example_q_widget.png b/src/napari_matplotlib/tests/baseline/test_example_q_widget.png deleted file mode 100644 index 5b3dcd94..00000000 Binary files a/src/napari_matplotlib/tests/baseline/test_example_q_widget.png and /dev/null differ diff --git a/src/napari_matplotlib/tests/baseline/test_histogram_2D.png b/src/napari_matplotlib/tests/baseline/test_histogram_2D.png index 5b3dcd94..f3f53aea 100644 Binary files a/src/napari_matplotlib/tests/baseline/test_histogram_2D.png and b/src/napari_matplotlib/tests/baseline/test_histogram_2D.png differ diff --git a/src/napari_matplotlib/tests/baseline/test_histogram_3D.png b/src/napari_matplotlib/tests/baseline/test_histogram_3D.png index 6a09711e..f9320022 100644 Binary files a/src/napari_matplotlib/tests/baseline/test_histogram_3D.png and b/src/napari_matplotlib/tests/baseline/test_histogram_3D.png differ diff --git a/src/napari_matplotlib/tests/baseline/test_scatter.png b/src/napari_matplotlib/tests/baseline/test_scatter.png index 1fd7d9e8..1977d45f 100644 Binary files a/src/napari_matplotlib/tests/baseline/test_scatter.png and b/src/napari_matplotlib/tests/baseline/test_scatter.png differ diff --git a/src/napari_matplotlib/tests/baseline/test_slice_2D.png b/src/napari_matplotlib/tests/baseline/test_slice_2D.png index 9636b891..de2cbd42 100644 Binary files a/src/napari_matplotlib/tests/baseline/test_slice_2D.png and b/src/napari_matplotlib/tests/baseline/test_slice_2D.png differ diff --git a/src/napari_matplotlib/tests/baseline/test_slice_3D.png b/src/napari_matplotlib/tests/baseline/test_slice_3D.png index 4b974fa4..f5077ea4 100644 Binary files a/src/napari_matplotlib/tests/baseline/test_slice_3D.png and b/src/napari_matplotlib/tests/baseline/test_slice_3D.png differ diff --git a/src/napari_matplotlib/tests/test_theme.py b/src/napari_matplotlib/tests/test_theme.py new file mode 100644 index 00000000..207171cf --- /dev/null +++ b/src/napari_matplotlib/tests/test_theme.py @@ -0,0 +1,20 @@ +import pytest + +from napari_matplotlib.base import NapariMPLWidget + + +@pytest.mark.parametrize( + "theme_name, expected_icons", + [("dark", "white"), ("light", "black")], +) +def test_theme_mpl_toolbar_icons( + make_napari_viewer, theme_name, expected_icons +): + """Check that the icons are taken from the correct folder for each napari theme.""" + viewer = make_napari_viewer() + viewer.theme = theme_name + path_to_icons = NapariMPLWidget(viewer)._get_path_to_icon() + assert path_to_icons.exists(), "The theme points to non-existant icons." + assert ( + path_to_icons.stem == expected_icons + ), "The theme is selecting unexpected icons." pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy