Skip to content

Commit c4a5368

Browse files
committed
Introduce a dependency on tinycss2. Then _use_ it via some convenience parsing finding functions.
1 parent b127776 commit c4a5368

File tree

5 files changed

+111
-4
lines changed

5 files changed

+111
-4
lines changed

setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ install_requires =
3030
matplotlib
3131
napari
3232
numpy
33+
tinycss2
3334
python_requires = >=3.8
3435
include_package_data = True
3536
package_dir =
@@ -56,6 +57,7 @@ testing =
5657
napari[pyqt5]
5758
pytest
5859
pytest-cov
60+
pytest-mock
5961
pytest-qt
6062
tox
6163
pytest-xvfb;sys_platform == 'linux'

src/napari_matplotlib/base.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from qtpy.QtGui import QIcon
1313
from qtpy.QtWidgets import QVBoxLayout, QWidget
1414

15-
from .util import Interval
15+
from .util import Interval, from_css_get_size_of
1616

1717
# Icons modified from
1818
# https://github.com/matplotlib/matplotlib/tree/main/lib/matplotlib/mpl-data/images
@@ -51,7 +51,9 @@ def __init__(self, napari_viewer: napari.viewer.Viewer):
5151

5252
self.canvas.figure.patch.set_facecolor("none")
5353
self.canvas.figure.set_layout_engine("constrained")
54-
self.toolbar = NapariNavigationToolbar(self.canvas, self)
54+
self.toolbar = NapariNavigationToolbar(
55+
self.canvas, self
56+
) # type: ignore[no-untyped-call]
5557
self._replace_toolbar_icons()
5658

5759
self.setLayout(QVBoxLayout())
@@ -188,6 +190,12 @@ def _replace_toolbar_icons(self) -> None:
188190
class NapariNavigationToolbar(NavigationToolbar2QT):
189191
"""Custom Toolbar style for Napari."""
190192

193+
def __init__(self, *args, **kwargs): # type: ignore[no-untyped-def]
194+
super().__init__(*args, **kwargs)
195+
self.setIconSize(
196+
from_css_get_size_of("QtViewerPushButton", fallback=(28, 28))
197+
)
198+
191199
def _update_buttons_checked(self) -> None:
192200
"""Update toggle tool icons when selected/unselected."""
193201
super()._update_buttons_checked()
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import napari.qt
2+
import pytest
3+
4+
from napari_matplotlib import HistogramWidget
5+
6+
7+
@pytest.mark.skip("Need to set the CSS")
8+
def test_stylesheet_is_set(make_napari_viewer):
9+
"""..."""
10+
viewer = make_napari_viewer()
11+
widget = HistogramWidget(viewer)
12+
expected = napari.qt.get_current_stylesheet()
13+
assert widget.toolbar.get_stylesheet() == expected
14+
15+
16+
def test_background_foreground(make_napari_viewer):
17+
viewer = make_napari_viewer()
18+
HistogramWidget(viewer)
19+
# widget.toolbar.
20+
assert True

src/napari_matplotlib/tests/test_util.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
2+
from qtpy.QtCore import QSize
23

3-
from napari_matplotlib.util import Interval
4+
from napari_matplotlib.util import Interval, from_css_get_size_of
45

56

67
def test_interval():
@@ -13,3 +14,18 @@ def test_interval():
1314

1415
with pytest.raises(ValueError, match="must be an integer"):
1516
"string" in interval # type: ignore
17+
18+
19+
def test_get_size_from_css(mocker):
20+
"""Test getting the max-width and max-height from something in css"""
21+
test_css = """
22+
Flibble {
23+
min-width : 0;
24+
max-width : 123px;
25+
min-height : 0px;
26+
max-height : 456px;
27+
padding: 0px;
28+
}
29+
"""
30+
mocker.patch("napari.qt.get_current_stylesheet").return_value = test_css
31+
assert from_css_get_size_of("Flibble", (1, 1)) == QSize(123, 456)

src/napari_matplotlib/util.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
from typing import Optional
1+
from typing import List, Optional, Tuple, Union
2+
3+
import napari.qt
4+
import tinycss2
5+
from qtpy.QtCore import QSize
26

37

48
class Interval:
@@ -34,3 +38,60 @@ def __contains__(self, val: int) -> bool:
3438
if self.upper is not None and val > self.upper:
3539
return False
3640
return True
41+
42+
43+
def _has_id(nodes: List[tinycss2.ast.Node], id_name: str) -> bool:
44+
"""
45+
Is `id_name` in IdentTokens in the list of CSS `nodes`?
46+
"""
47+
return any(
48+
[node.type == "ident" and node.value == id_name for node in nodes]
49+
)
50+
51+
52+
def _get_dimension(
53+
nodes: List[tinycss2.ast.Node], id_name: str
54+
) -> Union[int, None]:
55+
"""
56+
Get the value of the DimensionToken for the IdentToken `id_name`.
57+
58+
Returns
59+
-------
60+
None if no IdentToken is found.
61+
"""
62+
cleaned_nodes = [node for node in nodes if node.type != "whitespace"]
63+
for name, _, value, _ in zip(*(iter(cleaned_nodes),) * 4):
64+
if (
65+
name.type == "ident"
66+
and value.type == "dimension"
67+
and name.value == id_name
68+
):
69+
return value.int_value
70+
return None
71+
72+
73+
def from_css_get_size_of(
74+
qt_element_name: str, fallback: Tuple[int, int]
75+
) -> QSize:
76+
"""
77+
Get the size of `qt_element_name` from napari's current stylesheet.
78+
79+
TODO: Confirm that the napari.qt.get_current_stylesheet changes with napari
80+
theme (docs seem to indicate it should)
81+
82+
Returns
83+
-------
84+
QSize of the element if it's found, the `fallback` if it's not found..
85+
"""
86+
rules = tinycss2.parse_stylesheet(
87+
napari.qt.get_current_stylesheet(),
88+
skip_comments=True,
89+
skip_whitespace=True,
90+
)
91+
w, h = None, None
92+
for rule in rules:
93+
if _has_id(rule.prelude, qt_element_name):
94+
w = _get_dimension(rule.content, "max-width")
95+
h = _get_dimension(rule.content, "max-height")
96+
return QSize(w, h)
97+
return QSize(*fallback)

0 commit comments

Comments
 (0)