Source code for sepal_ui.sepalwidgets.sepalwidget
"""Custom sepalwidgets to add extra members to normal ``IpyvuetfiWidget``.
Gather the customized ``ipyvuetifyWidgets`` used toadd extra members to the existing one.
All the content of this modules is included in the parent ``sepal_ui.sepalwidgets`` package. So it can be imported directly from there.
Example:
.. jupyter-execute::
from sepal_ui import sepalwidgets as sw
sw.Tooltip(widget=sw.Btn(), tooltip="tooltip")
"""
import warnings
from typing import List, Optional, Type, Union
import ipyvuetify as v
import traitlets as t
from deprecated.sphinx import versionadded
from traitlets import observe
from typing_extensions import Self
__all__ = ["SepalWidget", "Tooltip"]
[docs]
class SepalWidget(v.VuetifyWidget):
viz: t.Bool = t.Bool(True).tag(sync=True)
"whether the widget is displayed or not"
old_class: t.Unicode = t.Unicode("").tag(sync=True)
"a saving attribute of the widget class"
with_tooltip: Optional[v.Tooltip] = None
"the full widget and its tooltip. Useful for display purposes when a tooltip has been set"
[docs]
def __init__(self, viz: bool = True, tooltip: str = "", **kwargs) -> None:
"""Custom vuetifyWidget to add specific methods.
Args:
viz: define if the widget should be visible or not
tooltip: tooltip text of the widget. If set the widget will be displayed on :code:`self.widget` (irreversible).
"""
# init the widget
super().__init__(**kwargs)
# setup the viz status
self.viz = viz
# create a tooltip only if a message is set
self.set_tooltip(tooltip)
@observe("viz")
def _set_viz(self, *args) -> None:
"""Hide or show the component according to its viz param value.
Hide the widget by reducing the html class to :code:`d-none`.
Show the widget by removing the :code:`d-none` html class.
Save the previous class
"""
# will be replaced byt direct calls to built-in hide
# once the previous custom implementation will be fully removed
if self.viz:
# change class value
self.class_ = self.old_class or self.class_
self.class_list.remove("d-none")
else:
# change class value
self.class_list.remove("d-none")
self.old_class = str(self.class_)
self.class_ = "d-none"
return
[docs]
def toggle_viz(self) -> Self:
"""Toggle the visibility of the widget."""
self.viz = not self.viz
return self
[docs]
def hide(self) -> Self:
"""Hide the widget by reducing the html class to :code:`d-none`.
Save the previous class and set viz attribute to False.
"""
# update viz state
self.viz = False
return self
[docs]
def show(self) -> Self:
"""Show the widget by removing the d-none html class.
Save the previous class and set viz attribute to True.
"""
# update viz state
self.viz = True
return self
[docs]
def reset(self) -> Self:
"""Clear the widget v_model.
Need to be extended in custom widgets to fit the structure of the actual input.
"""
self.v_model = None
return self
[docs]
def get_children(
self,
widget: Optional[v.VuetifyWidget] = None,
klass: Optional[Type[v.VuetifyWidget]] = None,
attr: str = "",
value: str = "",
id_: str = "",
elements: Optional[list] = None,
) -> List[v.VuetifyWidget]:
r"""Recursively search for every element matching the specifications.
multiple parameters can be used to search for matching elements. no error is raised if nothing is found.
Args:
widget: the widget to search into
klass: the vuetify widget class to look for . Leave empty for any.
attr: the attribute to look at. leave empty for no search
value: the value of the attr. ignored if attr is not set
elements: the list used to store found elements
Returns:
List containing all matching elements
Retrieve all children elements that matches with the given id\_.
Args:
id\_ (str, optional): attribute id to compare with.
Returns:
list with all matching elements if there are more than one, otherwise will return the matching element.
"""
# id_ was the previous variable it should continue working in this implementation
# remove kwargs when this will be deprecated
if id_ != "":
attr, value = ("id", id_)
warnings.warn(
'"id_" is a deprecated argument, use an ("attr", "value") pair instead',
DeprecationWarning,
)
# init the element list
elements = [] if elements is None else elements
# init the widget
widget = self if widget is None else widget
for w in widget.children:
# exit if children is not a widget (str, DOM objects.. etc)
if not isinstance(w, (v.VuetifyWidget, v.Html)):
continue
# compare the widget with requirements
# if no klass is specified, use both vuetifyWidget and Html objects
is_klass = (
isinstance(w, klass)
if klass
else isinstance(w, ((v.VuetifyWidget, v.Html)))
)
# using "niet" as default so that result is True if attr is Falsy
# "niet" is very unlikely to be used compared to None, False, "none"...
is_val = w.attributes.get(attr, "niet") == value if attr and value else True
not (is_klass and is_val) or elements.append(w)
# always search for nested elements
elements = self.get_children(w, klass, attr, value, id_, elements)
return elements
[docs]
def set_children(
self, children: Union[str, v.VuetifyWidget, list], position: str = "first"
) -> Self:
"""Insert input children in self children within given position.
Args:
children: the list of children to add to the widget. It can also be a list (str and DOMWidgets are accepted)
position: whether to insert as first or last element. ["first", "last"]
"""
if not isinstance(children, list):
children = [children]
new_childrens = self.children[:]
if position == "first":
new_childrens = children + new_childrens
elif position == "last":
new_childrens = new_childrens + children
else:
raise ValueError(
f"Position '{position}' is not a valid value. Use 'first' or 'last'"
)
self.children = new_childrens
return self
[docs]
@versionadded(version="2.9.0", reason="Tooltip are now integrated to widgets")
def set_tooltip(self, txt: str = "", **kwargs) -> v.Tooltip:
"""Create a tooltip associated with the widget.
If the text is not set, the tooltip will be automatically removed. Once the tooltip is set the object variable can be accessed normally but to render the widget, one will need to use :code:`self.with_tooltip` (irreversible).
Args:
txt: anything False (0, False, empty text, None) will lead to the removal of the tooltip. everything else will be used to fill the text area
kwargs: any options available in a Tooltip widget
Returns:
the tooltip associated with the object
"""
if isinstance(self.with_tooltip, Tooltip):
# If it's already created, and there are new kwargs, let's modify it
[setattr(self.with_tooltip, attr, value) for attr, value in kwargs.items()]
self.with_tooltip.children = [txt]
self.with_tooltip.disabled = not bool(txt)
elif txt != "":
self.with_tooltip = Tooltip(self, txt, **kwargs)
return self
[docs]
class Tooltip(v.Tooltip):
[docs]
def __init__(self, widget: v.VuetifyWidget, tooltip: str, **kwargs) -> None:
"""Custom widget to display tooltip when mouse is over widget.
Args:
widget: widget used to display tooltip
tooltip: the text to display in the tooltip
"""
# set some default parameters
kwargs.setdefault("close_delay", 200)
self.v_slots = [
{"name": "activator", "variable": "tooltip", "children": widget}
]
widget.v_on = "tooltip.on"
self.children = [tooltip]
super().__init__(**kwargs)