Skip to content

Commit 7bf5db1

Browse files
authored
Merge pull request #138 from samcunliffe/86-handling-of-light-themes
Match plugin text/axis/icon colours to the napari theme.
2 parents 1af4e64 + 010a02e commit 7bf5db1

29 files changed

+82
-17
lines changed

docs/changelog.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ New features
1111

1212
Visual improvements
1313
~~~~~~~~~~~~~~~~~~~
14-
- The background of ``napari-matplotlib`` figures and axes is now transparent.
14+
- The background of ``napari-matplotlib`` figures and axes is now transparent, and the text and axis colour respects the ``napari`` theme.
1515
- The icons in the Matplotlib toolbar are now the same size as icons in the napari window.
1616

1717
Changes

src/napari_matplotlib/base.py

Lines changed: 61 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ class BaseNapariMPLWidget(QWidget):
3838

3939
def __init__(
4040
self,
41+
napari_viewer: napari.Viewer,
4142
parent: Optional[QWidget] = None,
4243
):
4344
super().__init__(parent=parent)
45+
self.viewer = napari_viewer
4446

4547
self.canvas = FigureCanvas()
4648

@@ -50,6 +52,10 @@ def __init__(
5052
self.canvas, parent=self
5153
) # type: ignore[no-untyped-call]
5254
self._replace_toolbar_icons()
55+
# callback to update when napari theme changed
56+
# TODO: this isn't working completely (see issue #140)
57+
# most of our styling respects the theme change but not all
58+
self.viewer.events.theme.connect(self._on_theme_change)
5359

5460
self.setLayout(QVBoxLayout())
5561
self.layout().addWidget(self.toolbar)
@@ -69,25 +75,64 @@ def add_single_axes(self) -> None:
6975
self.axes = self.figure.subplots()
7076
self.apply_napari_colorscheme(self.axes)
7177

72-
@staticmethod
73-
def apply_napari_colorscheme(ax: Axes) -> None:
78+
def apply_napari_colorscheme(self, ax: Axes) -> None:
7479
"""Apply napari-compatible colorscheme to an Axes."""
80+
# get the foreground colours from current theme
81+
theme = napari.utils.theme.get_theme(self.viewer.theme, as_dict=False)
82+
fg_colour = theme.foreground.as_hex() # fg is a muted contrast to bg
83+
text_colour = theme.text.as_hex() # text is high contrast to bg
84+
7585
# changing color of axes background to transparent
7686
ax.set_facecolor("none")
7787

7888
# changing colors of all axes
7989
for spine in ax.spines:
80-
ax.spines[spine].set_color("white")
90+
ax.spines[spine].set_color(fg_colour)
8191

82-
ax.xaxis.label.set_color("white")
83-
ax.yaxis.label.set_color("white")
92+
ax.xaxis.label.set_color(text_colour)
93+
ax.yaxis.label.set_color(text_colour)
8494

8595
# changing colors of axes labels
86-
ax.tick_params(axis="x", colors="white")
87-
ax.tick_params(axis="y", colors="white")
96+
ax.tick_params(axis="x", colors=text_colour)
97+
ax.tick_params(axis="y", colors=text_colour)
98+
99+
def _on_theme_change(self) -> None:
100+
"""Update MPL toolbar and axis styling when `napari.Viewer.theme` is changed.
101+
102+
Note:
103+
At the moment we only handle the default 'light' and 'dark' napari themes.
104+
"""
105+
self._replace_toolbar_icons()
106+
if self.figure.gca():
107+
self.apply_napari_colorscheme(self.figure.gca())
108+
109+
def _theme_has_light_bg(self) -> bool:
110+
"""
111+
Does this theme have a light background?
112+
113+
Returns
114+
-------
115+
bool
116+
True if theme's background colour has hsl lighter than 50%, False if darker.
117+
"""
118+
theme = napari.utils.theme.get_theme(self.viewer.theme, as_dict=False)
119+
_, _, bg_lightness = theme.background.as_hsl_tuple()
120+
return bg_lightness > 0.5
121+
122+
def _get_path_to_icon(self) -> Path:
123+
"""
124+
Get the icons directory (which is theme-dependent).
125+
"""
126+
if self._theme_has_light_bg():
127+
return ICON_ROOT / "black"
128+
else:
129+
return ICON_ROOT / "white"
88130

89131
def _replace_toolbar_icons(self) -> None:
90-
# Modify toolbar icons and some tooltips
132+
"""
133+
Modifies toolbar icons to match the napari theme, and add some tooltips.
134+
"""
135+
icon_dir = self._get_path_to_icon()
91136
for action in self.toolbar.actions():
92137
text = action.text()
93138
if text == "Pan":
@@ -101,7 +146,7 @@ def _replace_toolbar_icons(self) -> None:
101146
"Click again to deactivate"
102147
)
103148
if len(text) > 0: # i.e. not a separator item
104-
icon_path = os.path.join(ICON_ROOT, text + ".png")
149+
icon_path = os.path.join(icon_dir, text + ".png")
105150
action.setIcon(QIcon(icon_path))
106151

107152

@@ -138,9 +183,7 @@ def __init__(
138183
napari_viewer: napari.viewer.Viewer,
139184
parent: Optional[QWidget] = None,
140185
):
141-
super().__init__(parent=parent)
142-
143-
self.viewer = napari_viewer
186+
super().__init__(napari_viewer=napari_viewer, parent=parent)
144187
self._setup_callbacks()
145188
self.layers: List[napari.layers.Layer] = []
146189

@@ -235,22 +278,24 @@ def __init__(self, *args, **kwargs): # type: ignore[no-untyped-def]
235278
def _update_buttons_checked(self) -> None:
236279
"""Update toggle tool icons when selected/unselected."""
237280
super()._update_buttons_checked()
281+
icon_dir = self.parentWidget()._get_path_to_icon()
282+
238283
# changes pan/zoom icons depending on state (checked or not)
239284
if "pan" in self._actions:
240285
if self._actions["pan"].isChecked():
241286
self._actions["pan"].setIcon(
242-
QIcon(os.path.join(ICON_ROOT, "Pan_checked.png"))
287+
QIcon(os.path.join(icon_dir, "Pan_checked.png"))
243288
)
244289
else:
245290
self._actions["pan"].setIcon(
246-
QIcon(os.path.join(ICON_ROOT, "Pan.png"))
291+
QIcon(os.path.join(icon_dir, "Pan.png"))
247292
)
248293
if "zoom" in self._actions:
249294
if self._actions["zoom"].isChecked():
250295
self._actions["zoom"].setIcon(
251-
QIcon(os.path.join(ICON_ROOT, "Zoom_checked.png"))
296+
QIcon(os.path.join(icon_dir, "Zoom_checked.png"))
252297
)
253298
else:
254299
self._actions["zoom"].setIcon(
255-
QIcon(os.path.join(ICON_ROOT, "Zoom.png"))
300+
QIcon(os.path.join(icon_dir, "Zoom.png"))
256301
)
6.76 KB
Loading
7.08 KB
Loading
6.65 KB
Loading
7.24 KB
Loading
7.14 KB
Loading
Loading
7.54 KB
Loading
6.88 KB
Loading

0 commit comments

Comments
 (0)