mirror of
https://github.com/liberatedsystems/openCom-Companion.git
synced 2024-11-30 09:10:36 +01:00
367 lines
10 KiB
Python
367 lines
10 KiB
Python
|
"""
|
||
|
Components/Tooltip
|
||
|
==================
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
`Material Design spec, Tooltips <https://material.io/components/tooltips>`_
|
||
|
|
||
|
.. rubric:: Tooltips display informative text when users hover over, focus on,
|
||
|
or tap an element.
|
||
|
|
||
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tooltip.png
|
||
|
:align: center
|
||
|
|
||
|
To use the :class:`~MDTooltip` class, you must create a new class inherited
|
||
|
from the :class:`~MDTooltip` class:
|
||
|
|
||
|
In Kv-language:
|
||
|
|
||
|
.. code-block:: kv
|
||
|
|
||
|
<TooltipMDIconButton@MDIconButton+MDTooltip>
|
||
|
|
||
|
In Python code:
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
class TooltipMDIconButton(MDIconButton, MDTooltip):
|
||
|
pass
|
||
|
|
||
|
.. Warning:: :class:`~MDTooltip` only works correctly with button and label classes.
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
from kivy.lang import Builder
|
||
|
|
||
|
from kivymd.app import MDApp
|
||
|
|
||
|
KV = '''
|
||
|
<TooltipMDIconButton@MDIconButton+MDTooltip>
|
||
|
|
||
|
|
||
|
MDScreen:
|
||
|
|
||
|
TooltipMDIconButton:
|
||
|
icon: "language-python"
|
||
|
tooltip_text: self.icon
|
||
|
pos_hint: {"center_x": .5, "center_y": .5}
|
||
|
'''
|
||
|
|
||
|
|
||
|
class Test(MDApp):
|
||
|
def build(self):
|
||
|
return Builder.load_string(KV)
|
||
|
|
||
|
|
||
|
Test().run()
|
||
|
|
||
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tooltip.gif
|
||
|
:align: center
|
||
|
|
||
|
.. Note:: The behavior of tooltips on desktop and mobile devices is different.
|
||
|
For more detailed information,
|
||
|
`click here <https://github.com/kivymd/KivyMD/wiki/Components-Tooltips>`_.
|
||
|
"""
|
||
|
|
||
|
__all__ = ("MDTooltip", "MDTooltipViewClass")
|
||
|
|
||
|
import os
|
||
|
from typing import Union
|
||
|
|
||
|
from kivy.animation import Animation
|
||
|
from kivy.clock import Clock
|
||
|
from kivy.core.window import Window
|
||
|
from kivy.lang import Builder
|
||
|
from kivy.metrics import dp
|
||
|
from kivy.properties import (
|
||
|
BoundedNumericProperty,
|
||
|
ColorProperty,
|
||
|
ListProperty,
|
||
|
NumericProperty,
|
||
|
OptionProperty,
|
||
|
StringProperty,
|
||
|
)
|
||
|
from kivy.uix.boxlayout import BoxLayout
|
||
|
|
||
|
from kivymd import uix_path
|
||
|
from kivymd.font_definitions import theme_font_styles
|
||
|
from kivymd.material_resources import DEVICE_TYPE
|
||
|
from kivymd.theming import ThemableBehavior
|
||
|
from kivymd.uix.behaviors import HoverBehavior, TouchBehavior
|
||
|
|
||
|
with open(
|
||
|
os.path.join(uix_path, "tooltip", "tooltip.kv"), encoding="utf-8"
|
||
|
) as kv_file:
|
||
|
Builder.load_string(kv_file.read())
|
||
|
|
||
|
|
||
|
class MDTooltip(ThemableBehavior, HoverBehavior, TouchBehavior):
|
||
|
tooltip_bg_color = ColorProperty(None)
|
||
|
"""
|
||
|
Tooltip background color in ``rgba`` format.
|
||
|
|
||
|
:attr:`tooltip_bg_color` is an :class:`~kivy.properties.ColorProperty`
|
||
|
and defaults to `None`.
|
||
|
"""
|
||
|
|
||
|
tooltip_text_color = ColorProperty(None)
|
||
|
"""
|
||
|
Tooltip text color in ``rgba`` format.
|
||
|
|
||
|
:attr:`tooltip_text_color` is an :class:`~kivy.properties.ColorProperty`
|
||
|
and defaults to `None`.
|
||
|
"""
|
||
|
|
||
|
tooltip_text = StringProperty()
|
||
|
"""
|
||
|
Tooltip text.
|
||
|
|
||
|
:attr:`tooltip_text` is an :class:`~kivy.properties.StringProperty`
|
||
|
and defaults to `''`.
|
||
|
"""
|
||
|
|
||
|
tooltip_font_style = OptionProperty("Caption", options=theme_font_styles)
|
||
|
"""
|
||
|
Tooltip font style. Available options are: `'H1'`, `'H2'`, `'H3'`, `'H4'`,
|
||
|
`'H5'`, `'H6'`, `'Subtitle1'`, `'Subtitle2'`, `'Body1'`, `'Body2'`,
|
||
|
`'Button'`, `'Caption'`, `'Overline'`, `'Icon'`.
|
||
|
|
||
|
:attr:`tooltip_font_style` is an :class:`~kivy.properties.OptionProperty`
|
||
|
and defaults to `'Caption'`.
|
||
|
"""
|
||
|
|
||
|
tooltip_radius = ListProperty(
|
||
|
[
|
||
|
dp(7),
|
||
|
]
|
||
|
)
|
||
|
"""
|
||
|
Corner radius values.
|
||
|
|
||
|
:attr:`radius` is an :class:`~kivy.properties.ListProperty`
|
||
|
and defaults to `[dp(7),]`.
|
||
|
"""
|
||
|
|
||
|
tooltip_display_delay = BoundedNumericProperty(0, min=0, max=4)
|
||
|
"""
|
||
|
Tooltip dsiplay delay.
|
||
|
|
||
|
:attr:`tooltip_display_delay` is an :class:`~kivy.properties.BoundedNumericProperty`
|
||
|
and defaults to `0`, min of `0` & max of `4`. This property only works on desktop.
|
||
|
"""
|
||
|
|
||
|
shift_y = NumericProperty()
|
||
|
"""
|
||
|
Y-offset of tooltip text.
|
||
|
|
||
|
:attr:`shift_y` is an :class:`~kivy.properties.NumericProperty`
|
||
|
and defaults to `0`.
|
||
|
"""
|
||
|
|
||
|
shift_right = NumericProperty()
|
||
|
"""
|
||
|
Shifting the tooltip text to the right.
|
||
|
|
||
|
.. versionadded:: 1.0.0
|
||
|
|
||
|
:attr:`shift_right` is an :class:`~kivy.properties.NumericProperty`
|
||
|
and defaults to `0`.
|
||
|
"""
|
||
|
|
||
|
shift_left = NumericProperty()
|
||
|
"""
|
||
|
Shifting the tooltip text to the left.
|
||
|
|
||
|
.. versionadded:: 1.0.0
|
||
|
|
||
|
:attr:`shift_left` is an :class:`~kivy.properties.NumericProperty`
|
||
|
and defaults to `0`.
|
||
|
"""
|
||
|
|
||
|
_tooltip = None
|
||
|
|
||
|
def __init__(self, **kwargs):
|
||
|
super().__init__(**kwargs)
|
||
|
self.register_event_type("on_show")
|
||
|
self.register_event_type("on_dismiss")
|
||
|
|
||
|
def delete_clock(self, widget, touch, *args):
|
||
|
if self.collide_point(touch.x, touch.y) and touch.grab_current:
|
||
|
try:
|
||
|
Clock.unschedule(touch.ud["event"])
|
||
|
except KeyError:
|
||
|
pass
|
||
|
self.on_leave()
|
||
|
|
||
|
def adjust_tooltip_position(self, x: float, y: float) -> tuple:
|
||
|
"""
|
||
|
Returns the coordinates of the tooltip that fit into the borders of the
|
||
|
screen.
|
||
|
"""
|
||
|
|
||
|
# If the position of the tooltip is outside the right border
|
||
|
# of the screen.
|
||
|
if x + self._tooltip.width > Window.width:
|
||
|
x = Window.width - (self._tooltip.width + dp(10))
|
||
|
else:
|
||
|
# If the position of the tooltip is outside the left border
|
||
|
# of the screen.
|
||
|
if x < 0:
|
||
|
x = "10dp"
|
||
|
# If the tooltip position is below bottom the screen border.
|
||
|
if y < 0:
|
||
|
y = dp(10)
|
||
|
# If the tooltip position is below top the screen border.
|
||
|
else:
|
||
|
if Window.height - self._tooltip.height < y:
|
||
|
y = Window.height - (self._tooltip.height + dp(10))
|
||
|
return x, y
|
||
|
|
||
|
def display_tooltip(self, interval: Union[int, float]) -> None:
|
||
|
if not self._tooltip or self._tooltip.parent:
|
||
|
return
|
||
|
|
||
|
Window.add_widget(self._tooltip)
|
||
|
pos = self.to_window(self.center_x, self.center_y)
|
||
|
|
||
|
if not self.shift_right and not self.shift_left:
|
||
|
x = pos[0] - (self._tooltip.width / 2)
|
||
|
else:
|
||
|
if self.shift_right:
|
||
|
x = pos[0] - (self._tooltip.width / 2) + self.shift_right
|
||
|
if self.shift_left:
|
||
|
x = pos[0] - (self._tooltip.width / 2) - self.shift_left
|
||
|
|
||
|
if not self.shift_y:
|
||
|
y = pos[1] - self._tooltip.height / 2 - self.height / 2 - dp(20)
|
||
|
else:
|
||
|
y = pos[1] - self._tooltip.height / 2 - self.height + self.shift_y
|
||
|
|
||
|
x, y = self.adjust_tooltip_position(x, y)
|
||
|
self._tooltip.pos = (x, y)
|
||
|
|
||
|
if DEVICE_TYPE == "desktop":
|
||
|
Clock.schedule_once(
|
||
|
self.animation_tooltip_show, self.tooltip_display_delay
|
||
|
)
|
||
|
else:
|
||
|
Clock.schedule_once(self.animation_tooltip_show, 0)
|
||
|
|
||
|
def animation_tooltip_show(self, interval: Union[int, float]) -> None:
|
||
|
"""Animation of opening tooltip on the screen."""
|
||
|
|
||
|
if self._tooltip:
|
||
|
(
|
||
|
Animation(_scale_x=1, _scale_y=1, d=0.1)
|
||
|
+ Animation(opacity=1, d=0.2)
|
||
|
).start(self._tooltip)
|
||
|
self.dispatch("on_show")
|
||
|
|
||
|
def animation_tooltip_dismiss(self, interval: Union[int, float]) -> None:
|
||
|
"""
|
||
|
.. versionadded:: 1.0.0
|
||
|
|
||
|
Animation of closing tooltip on the screen.
|
||
|
"""
|
||
|
|
||
|
if self._tooltip:
|
||
|
anim = Animation(_scale_x=0, _scale_y=0, d=0.1) + Animation(
|
||
|
opacity=0, d=0.2
|
||
|
)
|
||
|
anim.bind(on_complete=self._on_dismiss_anim_complete)
|
||
|
anim.start(self._tooltip)
|
||
|
|
||
|
def remove_tooltip(self, *args) -> None:
|
||
|
"""Removes the tooltip widget from the screen."""
|
||
|
|
||
|
Window.remove_widget(self._tooltip)
|
||
|
|
||
|
def on_long_touch(self, touch, *args) -> None:
|
||
|
if DEVICE_TYPE != "desktop":
|
||
|
self.on_enter(True)
|
||
|
|
||
|
def on_enter(self, *args) -> None:
|
||
|
"""
|
||
|
See
|
||
|
:attr:`~kivymd.uix.behaviors.hover_behavior.HoverBehavior.on_enter`
|
||
|
method in :class:`~kivymd.uix.behaviors.hover_behavior.HoverBehavior`
|
||
|
class.
|
||
|
"""
|
||
|
|
||
|
if not args and DEVICE_TYPE == "desktop":
|
||
|
if self.tooltip_text:
|
||
|
self._tooltip = MDTooltipViewClass(
|
||
|
tooltip_bg_color=self.tooltip_bg_color,
|
||
|
tooltip_text_color=self.tooltip_text_color,
|
||
|
tooltip_text=self.tooltip_text,
|
||
|
tooltip_font_style=self.tooltip_font_style,
|
||
|
tooltip_radius=self.tooltip_radius,
|
||
|
)
|
||
|
Clock.schedule_once(self.display_tooltip, -1)
|
||
|
|
||
|
def on_leave(self) -> None:
|
||
|
"""
|
||
|
See
|
||
|
:attr:`~kivymd.uix.behaviors.hover_behavior.HoverBehavior.on_leave`
|
||
|
method in :class:`~kivymd.uix.behaviors.hover_behavior.HoverBehavior`
|
||
|
class.
|
||
|
"""
|
||
|
|
||
|
if self._tooltip:
|
||
|
Clock.schedule_once(self.animation_tooltip_dismiss)
|
||
|
|
||
|
def on_show(self) -> None:
|
||
|
"""Default dismiss event handler."""
|
||
|
|
||
|
def on_dismiss(self) -> None:
|
||
|
"""
|
||
|
.. versionadded:: 1.0.0
|
||
|
|
||
|
Default dismiss event handler.
|
||
|
"""
|
||
|
|
||
|
def _on_dismiss_anim_complete(self, *args):
|
||
|
self.dispatch("on_dismiss")
|
||
|
self.remove_tooltip()
|
||
|
self._tooltip = None
|
||
|
|
||
|
|
||
|
class MDTooltipViewClass(ThemableBehavior, BoxLayout):
|
||
|
tooltip_bg_color = ColorProperty(None)
|
||
|
"""
|
||
|
See :attr:`~MDTooltip.tooltip_bg_color`.
|
||
|
"""
|
||
|
|
||
|
tooltip_text_color = ColorProperty(None)
|
||
|
"""
|
||
|
See :attr:`~MDTooltip.tooltip_text_color`.
|
||
|
"""
|
||
|
|
||
|
tooltip_text = StringProperty()
|
||
|
"""
|
||
|
See :attr:`~MDTooltip.tooltip_text`.
|
||
|
"""
|
||
|
|
||
|
tooltip_font_style = OptionProperty("Caption", options=theme_font_styles)
|
||
|
"""
|
||
|
See :attr:`~MDTooltip.tooltip_font_style`.
|
||
|
"""
|
||
|
|
||
|
tooltip_radius = ListProperty()
|
||
|
"""
|
||
|
See :attr:`~MDTooltip.tooltip_radius`.
|
||
|
"""
|
||
|
|
||
|
_scale_x = NumericProperty(0)
|
||
|
_scale_y = NumericProperty(0)
|
||
|
|
||
|
def __init__(self, **kwargs):
|
||
|
super().__init__(**kwargs)
|
||
|
self.padding = [
|
||
|
dp(8) if DEVICE_TYPE == "desktop" else dp(16),
|
||
|
dp(4),
|
||
|
dp(8) if DEVICE_TYPE == "desktop" else dp(16),
|
||
|
dp(4),
|
||
|
]
|