Skip to content

Commit d8d9330

Browse files
feat: Add dark mode when background is dark (#228)
Co-authored-by: pyansys-ci-bot <[email protected]>
1 parent 4f183e7 commit d8d9330

28 files changed

+111
-50
lines changed

doc/changelog.d/228.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
feat: Add dark mode when background is dark

src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555

5656
_HAS_TRAME = importlib.util.find_spec("pyvista.trame") and importlib.util.find_spec("trame.app")
5757

58+
DARK_MODE_THRESHOLD = 120
59+
5860
if TYPE_CHECKING:
5961
import numpy as np
6062

@@ -179,25 +181,31 @@ def scene(self) -> pv.Plotter:
179181
"""PyVista scene."""
180182
return self._pl.scene
181183

182-
def enable_widgets(self):
183-
"""Enable the widgets for the plotter."""
184+
def enable_widgets(self, dark_mode: bool = False) -> None:
185+
"""Enable the widgets for the plotter.
186+
187+
Parameters
188+
----------
189+
dark_mode : bool, default: False
190+
Whether to use dark mode for the widgets.
191+
"""
184192
# Create Plotter widgets
185193
if self._enable_widgets:
186194
self._widgets: List[PlotterWidget] = []
187-
self._widgets.append(Ruler(self._pl._scene))
195+
self._widgets.append(Ruler(self._pl._scene, dark_mode))
188196
[
189-
self._widgets.append(DisplacementArrow(self._pl._scene, direction=dir))
197+
self._widgets.append(DisplacementArrow(self._pl._scene, dir, dark_mode))
190198
for dir in CameraPanDirection
191199
]
192200
[
193-
self._widgets.append(ViewButton(self._pl._scene, direction=dir))
201+
self._widgets.append(ViewButton(self._pl._scene, dir, dark_mode))
194202
for dir in ViewDirection
195203
]
196-
self._widgets.append(MeasureWidget(self))
197-
self._widgets.append(ScreenshotButton(self))
204+
self._widgets.append(MeasureWidget(self, dark_mode))
205+
self._widgets.append(ScreenshotButton(self, dark_mode))
198206
if not self._use_qt:
199-
self._widgets.append(MeshSliderWidget(self))
200-
self._widgets.append(HideButton(self))
207+
self._widgets.append(MeshSliderWidget(self, dark_mode))
208+
self._widgets.append(HideButton(self, dark_mode))
201209

202210
def add_widget(self, widget: Union[PlotterWidget, List[PlotterWidget]]):
203211
"""Add one or more custom widgets to the plotter.
@@ -403,6 +411,7 @@ def show(
403411
screenshot: Optional[str] = None,
404412
view_2d: Dict = None,
405413
name_filter: str = None,
414+
dark_mode: bool = False,
406415
**plotting_options,
407416
) -> List[Any]:
408417
"""Plot and show any PyAnsys object.
@@ -420,6 +429,8 @@ def show(
420429
Dictionary with the plane and the viewup vectors of the 2D plane.
421430
name_filter : str, default: None
422431
Regular expression with the desired name or names to include in the plotter.
432+
dark_mode : bool, default: False
433+
Whether to use dark mode for the widgets.
423434
**plotting_options : dict, default: None
424435
Keyword arguments. For allowable keyword arguments, see the
425436
:meth:`Plotter.add_mesh <pyvista.Plotter.add_mesh>` method.
@@ -446,7 +457,16 @@ def show(
446457
)
447458
# Enable widgets and picking capabilities
448459
if screenshot is None and not ansys.tools.visualization_interface.DOCUMENTATION_BUILD:
449-
self.enable_widgets()
460+
if dark_mode:
461+
self.enable_widgets(dark_mode=dark_mode)
462+
elif all([
463+
color < DARK_MODE_THRESHOLD
464+
for color in self._pl.scene.background_color.int_rgb
465+
]):
466+
print([color for color in self._pl.scene.background_color.int_rgb])
467+
self.enable_widgets(dark_mode=True)
468+
else:
469+
self.enable_widgets()
450470

451471
if self._allow_picking:
452472
self.enable_picking()
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading

src/ansys/tools/visualization_interface/backends/pyvista/widgets/button.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,15 @@ class Button(PlotterWidget):
4343
Plotter to draw the buttons on.
4444
button_config : tuple
4545
Tuple containing the position and the path to the icon of the button.
46+
dark_mode : bool, optional
47+
Whether to activate the dark mode or not.
4648
4749
"""
4850

49-
def __init__(self, plotter: Plotter, button_config: tuple):
51+
def __init__(self, plotter: Plotter, button_config: tuple, dark_mode: bool = False) -> None:
5052
"""Initialize the ``Button`` class."""
5153
super().__init__(plotter)
54+
self._dark_mode = dark_mode
5255
self._button: vtkButtonWidget = self.plotter.add_checkbox_button_widget(
5356
self.callback, position=button_config.value[2], size=30, border_size=3
5457
)
@@ -68,9 +71,13 @@ def callback(self, state: bool) -> None:
6871

6972
def update(self) -> None:
7073
"""Assign the image that represents the button."""
74+
if self._dark_mode:
75+
is_inv = "_inv"
76+
else:
77+
is_inv = ""
7178
button_repr = self._button.GetRepresentation()
7279
button_icon_path = Path(
73-
Path(__file__).parent / "_images", self.button_config.value[1]
80+
Path(__file__).parent / "_images", f"{self.button_config.value[1]}{is_inv}.png"
7481
)
7582
button_icon = vtkPNGReader()
7683
button_icon.SetFileName(button_icon_path)

src/ansys/tools/visualization_interface/backends/pyvista/widgets/displace_arrows.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@
3030
class CameraPanDirection(Enum):
3131
"""Provides an enum with the available movement directions of the camera."""
3232

33-
XUP = 0, "upxarrow.png", (5, 170)
34-
XDOWN = 1, "downarrow.png", (5, 130)
35-
YUP = 2, "upyarrow.png", (35, 170)
36-
YDOWN = 3, "downarrow.png", (35, 130)
37-
ZUP = 4, "upzarrow.png", (65, 170)
38-
ZDOWN = 5, "downarrow.png", (65, 130)
33+
XUP = 0, "upxarrow", (5, 170)
34+
XDOWN = 1, "downarrow", (5, 130)
35+
YUP = 2, "upyarrow", (35, 170)
36+
YDOWN = 3, "downarrow", (35, 130)
37+
ZUP = 4, "upzarrow", (65, 170)
38+
ZDOWN = 5, "downarrow", (65, 130)
3939

4040

4141
class DisplacementArrow(Button):
@@ -47,12 +47,13 @@ class DisplacementArrow(Button):
4747
Plotter to draw the buttons on.
4848
direction : CameraPanDirection
4949
Direction that the camera is to move.
50-
50+
dark_mode : bool, optional
51+
Whether to activate the dark mode or not.
5152
"""
5253

53-
def __init__(self, plotter: Plotter, direction: CameraPanDirection):
54+
def __init__(self, plotter: Plotter, direction: CameraPanDirection, dark_mode: bool = False) -> None:
5455
"""Initialize the ``DisplacementArrow`` class."""
55-
super().__init__(plotter, direction)
56+
super().__init__(plotter, direction, dark_mode)
5657
self.direction = direction
5758
self.update()
5859

src/ansys/tools/visualization_interface/backends/pyvista/widgets/hide_buttons.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,16 @@ class HideButton(PlotterWidget):
3838
----------
3939
plotter_helper : PlotterHelper
4040
Plotter to add the hide widget to.
41+
dark_mode : bool, optional
42+
Whether to activate the dark mode or not.
4143
4244
"""
4345

44-
def __init__(self, plotter: "Plotter") -> None:
46+
def __init__(self, plotter: "Plotter", dark_mode: bool = False) -> None:
4547
"""Initialize the ``HideButton`` class."""
4648
# Call PlotterWidget ctor
4749
super().__init__(plotter._pl.scene)
48-
50+
self._dark_mode = dark_mode
4951
# Initialize variables
5052
self._actor: vtkActor = None
5153
self._plotter = plotter
@@ -70,17 +72,22 @@ def callback(self, state: bool) -> None:
7072
widget._button.GetRepresentation().SetVisibility(0)
7173
else:
7274
for widget in self.plotter._widgets:
73-
widget._button.On()
74-
widget._button.GetRepresentation().SetVisibility(1)
75+
widget._button.On()
76+
widget._button.GetRepresentation().SetVisibility(1)
7577

7678
def update(self) -> None:
7779
"""Define the hide widget button parameters."""
80+
if self._dark_mode:
81+
is_inv = "_inv"
82+
else:
83+
is_inv = ""
84+
7885
show_vr = self._button.GetRepresentation()
7986
show_vison_icon_file = Path(
80-
Path(__file__).parent / "_images"/ "visibilityon.png"
87+
Path(__file__).parent / "_images" / f"visibilityon{is_inv}.png"
8188
)
8289
show_visoff_icon_file = Path(
83-
Path(__file__).parent / "_images"/ "visibilityoff.png"
90+
Path(__file__).parent / "_images" / f"visibilityon{is_inv}.png"
8491
)
8592
show_r_on = vtkPNGReader()
8693
show_r_on.SetFileName(show_vison_icon_file)

src/ansys/tools/visualization_interface/backends/pyvista/widgets/measure.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,16 @@ class MeasureWidget(PlotterWidget):
3838
----------
3939
plotter_helper : PlotterHelper
4040
Plotter to add the measure widget to.
41+
dark_mode : bool, optional
42+
Whether to activate the dark mode or not.
4143
4244
"""
4345

44-
def __init__(self, plotter_helper: "Plotter") -> None:
46+
def __init__(self, plotter_helper: "Plotter", dark_mode: bool = False) -> None:
4547
"""Initialize the ``MeasureWidget`` class."""
4648
# Call PlotterWidget ctor
4749
super().__init__(plotter_helper._pl.scene)
48-
50+
self._dark_mode = dark_mode
4951
# Initialize variables
5052
self._actor: vtkActor = None
5153
self.plotter_helper = plotter_helper
@@ -84,9 +86,13 @@ def callback(self, state: bool) -> None:
8486

8587
def update(self) -> None:
8688
"""Define the measurement widget button parameters."""
89+
if self._dark_mode:
90+
is_inv = "_inv"
91+
else:
92+
is_inv = ""
8793
show_measure_vr = self._button.GetRepresentation()
8894
show_measure_icon_file = Path(
89-
Path(__file__).parent / "_images"/ "measurement.png"
95+
Path(__file__).parent / "_images" / f"measurement{is_inv}.png"
9096
)
9197
show_measure_r = vtkPNGReader()
9298
show_measure_r.SetFileName(show_measure_icon_file)

src/ansys/tools/visualization_interface/backends/pyvista/widgets/mesh_slider.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,16 @@ class MeshSliderWidget(PlotterWidget):
3838
----------
3939
plotter_helper : PlotterHelper
4040
Plotter to add the mesh slider widget to.
41+
dark_mode : bool, optional
42+
Whether to activate the dark mode or not.
4143
4244
"""
4345

44-
def __init__(self, plotter_helper: "Plotter") -> None:
46+
def __init__(self, plotter_helper: "Plotter", dark_mode: bool = False) -> None:
4547
"""Initialize the ``MeshSliderWidget`` class."""
4648
# Call PlotterWidget ctor
4749
super().__init__(plotter_helper._pl.scene)
48-
50+
self._dark_mode = dark_mode
4951
# Initialize variables
5052
self._widget_actor: vtkActor = None
5153
self.plotter_helper = plotter_helper
@@ -101,9 +103,13 @@ def callback(self, state: bool) -> None:
101103

102104
def update(self) -> None:
103105
"""Define the mesh slider widget button parameters."""
106+
if self._dark_mode:
107+
is_inv = "_inv"
108+
else:
109+
is_inv = ""
104110
show_measure_vr = self._button.GetRepresentation()
105111
show_measure_icon_file = Path(
106-
Path(__file__).parent / "_images"/ "planecut.png"
112+
Path(__file__).parent / "_images" / f"planecut{is_inv}.png"
107113
)
108114
show_measure_r = vtkPNGReader()
109115
show_measure_r.SetFileName(show_measure_icon_file)

src/ansys/tools/visualization_interface/backends/pyvista/widgets/ruler.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,16 @@ class Ruler(PlotterWidget):
3636
----------
3737
plotter : ~pyvista.Plotter
3838
Provides the plotter to add the ruler widget to.
39+
dark_mode : bool, optional
40+
Whether to activate the dark mode or not.
3941
4042
"""
4143

42-
def __init__(self, plotter: Plotter) -> None:
44+
def __init__(self, plotter: Plotter, dark_mode: bool = False) -> None:
4345
"""Initialize the ``Ruler`` class."""
4446
# Call PlotterWidget ctor
4547
super().__init__(plotter)
46-
48+
self._dark_mode = dark_mode
4749
# Initialize variables
4850
self._actor: vtkActor = None
4951
self._button: vtkButtonWidget = self.plotter.add_checkbox_button_widget(
@@ -76,16 +78,20 @@ def callback(self, state: bool) -> None:
7678
show_xaxis=True,
7779
show_yaxis=True,
7880
show_zaxis=True,
79-
color="black",
81+
color="white" if self._dark_mode else "black",
8082
xtitle="X Axis [m]",
8183
ytitle="Y Axis [m]",
8284
ztitle="Z Axis [m]",
8385
)
8486

8587
def update(self) -> None:
8688
"""Define the configuration and representation of the ruler widget button."""
89+
if self._dark_mode:
90+
is_inv = "_inv"
91+
else:
92+
is_inv = ""
8793
show_ruler_vr = self._button.GetRepresentation()
88-
show_ruler_icon_file = Path(Path(__file__).parent / "_images" / "ruler.png")
94+
show_ruler_icon_file = Path(Path(__file__).parent / "_images" / f"ruler{is_inv}.png")
8995
show_ruler_r = vtkPNGReader()
9096
show_ruler_r.SetFileName(show_ruler_icon_file)
9197
show_ruler_r.Update()

src/ansys/tools/visualization_interface/backends/pyvista/widgets/screenshot.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,16 @@ class ScreenshotButton(PlotterWidget):
3636
----------
3737
plotter : ~pyvista.Plotter
3838
Provides the plotter to add the screenshot widget to.
39+
dark_mode : bool, optional
40+
Whether to activate the dark mode or not.
3941
4042
"""
4143

42-
def __init__(self, plotter: Plotter) -> None:
44+
def __init__(self, plotter: Plotter, dark_mode: bool = False) -> None:
4345
"""Initialize the ``ScreenshotButton`` class."""
4446
# Call PlotterWidget ctor
4547
super().__init__(plotter)
46-
48+
self._dark_mode = dark_mode
4749
# Initialize variables
4850
self._actor: vtkActor = None
4951
self._button: vtkButtonWidget = self.plotter._pl.scene.add_checkbox_button_widget(
@@ -69,8 +71,12 @@ def callback(self, state: bool) -> None:
6971

7072
def update(self) -> None:
7173
"""Define the configuration and representation of the screenshot widget button."""
74+
if self._dark_mode:
75+
is_inv = "_inv"
76+
else:
77+
is_inv = ""
7278
show_vr = self._button.GetRepresentation()
73-
show_icon_file = Path(Path(__file__).parent / "_images" / "screenshot.png")
79+
show_icon_file = Path(Path(__file__).parent / "_images" / f"screenshot{is_inv}.png")
7480
show_r = vtkPNGReader()
7581
show_r.SetFileName(show_icon_file)
7682
show_r.Update()

src/ansys/tools/visualization_interface/backends/pyvista/widgets/view_button.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@
3131
class ViewDirection(Enum):
3232
"""Provides an enum with the available views."""
3333

34-
XYPLUS = 0, "+xy.png", (5, 220)
35-
XYMINUS = 1, "-xy.png", (5, 251)
36-
XZPLUS = 2, "+xz.png", (5, 282)
37-
XZMINUS = 3, "-xz.png", (5, 313)
38-
YZPLUS = 4, "+yz.png", (5, 344)
39-
YZMINUS = 5, "-yz.png", (5, 375)
40-
ISOMETRIC = 6, "isometric.png", (5, 406)
34+
XYPLUS = 0, "+xy", (5, 220)
35+
XYMINUS = 1, "-xy", (5, 251)
36+
XZPLUS = 2, "+xz", (5, 282)
37+
XZMINUS = 3, "-xz", (5, 313)
38+
YZPLUS = 4, "+yz", (5, 344)
39+
YZMINUS = 5, "-yz", (5, 375)
40+
ISOMETRIC = 6, "isometric", (5, 406)
4141

4242

4343
class ViewButton(Button):
@@ -49,12 +49,13 @@ class ViewButton(Button):
4949
Plotter to draw the buttons on.
5050
direction : ViewDirection
5151
Direction of the view.
52-
52+
dark_mode : bool, optional
53+
Whether to activate the dark mode or not.
5354
"""
5455

55-
def __init__(self, plotter: Plotter, direction: tuple):
56+
def __init__(self, plotter: Plotter, direction: tuple, dark_mode: bool = False) -> None:
5657
"""Initialize the ``ViewButton`` class."""
57-
super().__init__(plotter, direction)
58+
super().__init__(plotter, direction, dark_mode)
5859
self.direction = direction
5960
self.update()
6061

0 commit comments

Comments
 (0)